Add status text and prework for moroe graphing

This commit is contained in:
Marco van Dijk 2022-04-30 13:44:16 +02:00
parent 5304f7252b
commit 00e84fe122
6 changed files with 364 additions and 3 deletions

View File

@ -4,7 +4,8 @@ import Startup from './pages/loadingScreen.js';
import Grafana from './pages/grafana.js'; import Grafana from './pages/grafana.js';
import Livepeer from './pages/livepeer.js'; import Livepeer from './pages/livepeer.js';
import Stats from './pages/stats.js'; import Stats from './pages/stats.js';
import Summary from './pages/summary.js' import Summary from './pages/summary.js';
import Graphs from './pages/graph.js';
import { import {
BrowserRouter as Router, BrowserRouter as Router,
@ -22,6 +23,7 @@ export default function App() {
<Route exact path='/tickets' element={<Stats />} /> <Route exact path='/tickets' element={<Stats />} />
<Route exact path='/stats' element={<Stats />} /> <Route exact path='/stats' element={<Stats />} />
<Route exact path='/summary' element={<Summary />} /> <Route exact path='/summary' element={<Summary />} />
<Route exact path='/graphs' element={<Graphs />} />
<Route exact path='/orchestrator' element={<Grafana />} /> <Route exact path='/orchestrator' element={<Grafana />} />
<Route path='/' element={<Home />} /> <Route path='/' element={<Home />} />
</Routes> </Routes>

View File

@ -0,0 +1,14 @@
import React, { useState } from 'react';
import { useSelector } from 'react-redux';
const CommissionGraph = (obj) => {
return (
<div className="stroke fullMargin insetEffect" style={{ padding: 0, margin: 0 }}>
</div>
)
}
export default CommissionGraph;

View File

@ -10,7 +10,6 @@ import { Pagination } from "@mantine/core";
const itemsPerPage = 10; const itemsPerPage = 10;
const MonthlyOrchestrators = (obj) => { const MonthlyOrchestrators = (obj) => {
const livepeer = useSelector((state) => state.livepeerstate);
const [thisScores, setThisScores] = useState(null); const [thisScores, setThisScores] = useState(null);
const [activePage, setPage] = useState(1); const [activePage, setPage] = useState(1);

View File

@ -0,0 +1,163 @@
import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import { VictoryLine, VictoryChart, VictoryLegend } from 'victory';
const TotalStakeGraph = (obj) => {
const livepeer = useSelector((state) => state.livepeerstate);
const getName = (address) => {
let thisDomain = null;
// Lookup domain in cache
if (livepeer.ensDomainMapping) {
for (const thisAddr of livepeer.ensDomainMapping) {
if (thisAddr.address === address) {
thisDomain = thisAddr;
break;
}
}
}
// Lookup current info in cache only if this addr has a mapped ENS domain
if (thisDomain && thisDomain.domain) {
for (const thisAddr of livepeer.ensInfoMapping) {
if (thisAddr.domain === thisDomain.domain) {
if (thisAddr.domain.length > 18) {
return (thisAddr.domain.substring(0, 16) + "..");
}
return thisAddr.domain;
}
}
}
if (livepeer.threeBoxInfo) {
for (const thisAddr of livepeer.threeBoxInfo) {
if (thisAddr.address === address) {
if (thisAddr.name) {
if (thisAddr.name.length > 18) {
return (thisAddr.name.substring(0, 16) + "..");
}
return thisAddr.name;
} else {
return (address.substring(0, 16) + "..");
}
break;
}
}
}
return (address.substring(0, 16) + "..");
}
const colors = [
"#282678",
"#56256c",
"#872974",
"#b63074",
"#de416d",
"#ff5c5f",
"#ffe085",
"#f0e575",
"#daec69",
"#bef262",
"#97f964",
"#5cff6f",
"#14ff47",
"#00fb94",
"#00f3cd",
"#00e7f3",
"#00d8ff",
"#24c8ff",
"#0a78ff",
"#5864d7",
"#6952b1",
"#69448d",
"#61386c",
"#522f50"
]
let listOfNames = [];
let listOfLists = [];
let listToParse = [...obj.data];
// Turn unprocessed list into a per orchestrator list
while (listToParse.length) {
// Take first orchestrator as the current orchestrator
const currentOrchestrator = listToParse[0].address;
let thisIdx = listToParse.length - 1;
let curData = [];
// Split of all orchestrators
while (thisIdx >= 0) {
const cur = listToParse[thisIdx];
if (cur.address == currentOrchestrator) {
const thisDate = new Date(cur.timestamp);
curData.push({
x: thisDate,
y: cur.totalStake.toFixed(0)
});
listToParse.splice(thisIdx, 1);
}
thisIdx--;
}
if (curData.length) {
listOfNames.push(currentOrchestrator);
listOfLists.push(curData);
}
}
let legendData = [];
return (
<div className="stroke fullMargin insetEffect" style={{ padding: 0, margin: 0 }}>
<div className="row" style={{ marginTop: '1em', marginBottom: '1em', minHeight: '70vh', height: '100%' }}>
<div className="stroke" style={{ width: 'unset' }}>
<h4>Total stake over time</h4>
<VictoryChart
width={1920}
height={1080}
padding={100}
fixLabelOverlap={true}
>
{listOfLists.map((thisData, thisIndex) => {
if (thisIndex < 100) {
legendData.push({ name: getName(listOfNames[thisIndex]), symbol: { fill: colors[thisIndex % colors.length] } });
return (
<VictoryLine
key={"orch" + thisIndex}
padding={0}
data={thisData}
standalone={false}
style={{
backgroundColor: 'rgba(122, 128, 127, 0.4)',
data: {
fillOpacity: 0.9, stroke: colors[thisIndex % colors.length], strokeWidth: 1.5
},
labels: {
fontSize: 24, zIndex: 999
}
}}
/>
)
} else {
return null;
}
})}
</VictoryChart>
<div className='row'>
<VictoryLegend
title="Legend"
centerTitle
orientation="horizontal"
gutter={20}
width={1920}
fixLabelOverlap={true}
height={1080}
padding={100}
style={{ border: { stroke: "black" }, title: { fontSize: 20 } }}
data={legendData}
/>
</div>
</div>
</div>
</div>
)
}
export default TotalStakeGraph;

164
src/pages/graph.js Normal file
View File

@ -0,0 +1,164 @@
import React, { useState } from 'react'
import '../style.css';
import { Navigate } from "react-router-dom";
import { useSelector } from 'react-redux';
import { SegmentedControl } from "@mantine/core";
import CommissionGraph from '../components/CommissionGraph';
import TotalStakeGraph from '../components/TotalStakeGraph';
const Graphs = (obj) => {
const livepeer = useSelector((state) => state.livepeerstate);
const [redirectToHome, setRedirectToHome] = useState(false);
const [showOnlyTranscoders, setShowOnlyTranscoders] = useState(true);
const [activePage, setPage] = useState(1);
let thisColour;
if (activePage == 1) {
thisColour = "teal";
} else if (activePage == 2) {
thisColour = "indigo";
} else if (activePage == 3) {
thisColour = "gray";
}
console.log("Rendering Graphs Viewer");
if (redirectToHome) {
return <Navigate push to="/" />;
}
return (
<div style={{ margin: 0, padding: 0, height: '100%', width: '100%', overflow: 'hidden' }}>
<div id='header'>
<div className='rowAlignLeft'>
<button className="homeButton" onClick={() => {
setRedirectToHome(true);
}}>
<h1>🏠</h1>
</button>
<h4 className="rowAlignLeft withWrap showNeverOnMobile">Statistics</h4>
</div>
<div className='rowAlignRight'>
<p>Hide non-earners</p>
<div className="toggle-container" onClick={() => setShowOnlyTranscoders(!showOnlyTranscoders)}>
<div className={`dialog-button ${showOnlyTranscoders ? "" : "disabled"}`}>
{showOnlyTranscoders ? "Show" : "Hide"}
</div>
</div>
</div>
</div>
<div id='bodyContent'>
<div className="row">
<div className="stroke roundedOpaque onlyVerticalScroll" style={{ width: '100vw', height: 'calc( 100vh - 50px - 2em)', marginTop: '2em' }}>
<div className="content-wrapper" style={{ width: '100%' }}>
<div className="overflow-container">
<div className="overflow-content" style={{ padding: 0 }}>
<div className="stroke" key={obj.seed + "menu"} style={{ marginTop: '0', paddingTop: '0' }}>
<div className="row" style={{ margin: '0' }}>
<SegmentedControl
styles={{
root: { backgroundColor: 'rgba(103, 103, 103, 0.6)', border: 'none', borderColor: 'transparent' },
label: {
color: 'black',
border: 'none',
'&:hover': {
color: 'black',
backgroundColor: 'rgba(56, 56, 56, 0.2)',
border: 'none'
},
'&': {
color: 'black',
border: 'none'
}
},
labelActive: {
color: 'black',
border: 'none',
'&:hover': {
color: 'black',
backgroundColor: 'rgba(56, 56, 56, 0.2)',
border: 'none'
},
'&': {
color: 'black',
border: 'none'
}
},
input: {
},
control: {
color: 'black',
border: 'none',
'&:hover': {
color: 'black',
border: 'none'
},
'&': {
color: 'black',
border: 'none'
},
'&:not(:first-of-type)': {
color: 'black',
border: 'none'
}
},
controlActive: {
color: 'black',
border: 'none',
'&:hover': {
color: 'black',
border: 'none'
},
'&': {
color: 'black',
border: 'none'
}
},
active: {
},
disabled: {
},
}}
value={activePage}
onChange={setPage}
spacing="lg"
size="lg"
radius={0}
transitionDuration={200}
transitionTimingFunction="linear"
color={thisColour}
data={[
{ label: 'Commission', value: '1' },
{ label: 'Stake', value: '2' }
]}
/>
</div>
{
activePage == 1 ? <CommissionGraph
data={livepeer.allCommissions}
seed={"commission"}
/> : null
}
{
activePage == 2 ? <TotalStakeGraph
data={livepeer.allTotalStakes}
seed={"totalstake"}
/> : null
}
<div className="verticalDivider" />
</div>
</div>
</div>
</div>
</div>
</div>
</div >
</div >
);
}
export default Graphs;

View File

@ -15,6 +15,7 @@ const Home = (obj) => {
const [redirectToGrafana, setRedirectToGrafana] = useState(false); const [redirectToGrafana, setRedirectToGrafana] = useState(false);
const [redirectToLPT, setRedirectToLPT] = useState(false); const [redirectToLPT, setRedirectToLPT] = useState(false);
const [redirectToStats, setRedirectToStats] = useState(false); const [redirectToStats, setRedirectToStats] = useState(false);
const [redirectToGraphs, setRedirectToGraphs] = useState(false);
if (redirectToGrafana) { if (redirectToGrafana) {
return <Navigate push to="/orchestrator" />; return <Navigate push to="/orchestrator" />;
} }
@ -24,6 +25,10 @@ const Home = (obj) => {
if (redirectToStats) { if (redirectToStats) {
return <Navigate push to="/stats" />; return <Navigate push to="/stats" />;
} }
if (redirectToGraphs) {
return <Navigate push to="/graphs" />;
}
// Get amount of unique IP's which have visited this website // Get amount of unique IP's which have visited this website
var totalVisitorCount = 0; var totalVisitorCount = 0;
if (userstate.visitorStats) { if (userstate.visitorStats) {
@ -38,7 +43,7 @@ const Home = (obj) => {
</div> </div>
<div className="verticalDivider" /> <div className="verticalDivider" />
<div className="flexContainer"> <div className="flexContainer">
<div className="stroke roundedOpaque"> <div className="stroke roundedOpaque" style={{maxWidth: '400px'}}>
<div className="verticalDivider" /> <div className="verticalDivider" />
<div className="row"> <div className="row">
<h3>Home</h3> <h3>Home</h3>
@ -71,11 +76,25 @@ const Home = (obj) => {
<p>📈 Statistics 💰</p> <p>📈 Statistics 💰</p>
</button> </button>
</div> </div>
<div className="row">
<button className="waveButton" onClick={() => {
setRedirectToGraphs(true);
}}>
<p>📉 Graphs 📊</p>
</button>
</div>
<div className="verticalDivider" /> <div className="verticalDivider" />
<div className="row"> <div className="row">
<ContractPrices quotes={livepeer.quotes} blockchains={livepeer.blockchains} /> <ContractPrices quotes={livepeer.quotes} blockchains={livepeer.blockchains} />
</div> </div>
<div className="verticalDivider" /> <div className="verticalDivider" />
<div className="row">
<h3>Status</h3>
</div>
<div className="row">
<p>Currently there is an issue with Events getting duplicated. The website will have inflated statistics and become unavailable from time to time while we are working on a fix</p>
</div>
<div className="verticalDivider" />
</div> </div>
</div> </div>
<div className="alwaysOnBottom showNeverOnMobile"> <div className="alwaysOnBottom showNeverOnMobile">