mirror of
https://github.com/stronk-dev/LivepeerEvents.git
synced 2025-07-05 10:45:10 +02:00
Add status text and prework for moroe graphing
This commit is contained in:
parent
5304f7252b
commit
00e84fe122
@ -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>
|
||||||
|
14
src/components/CommissionGraph.js
Normal file
14
src/components/CommissionGraph.js
Normal 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;
|
@ -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);
|
||||||
|
|
||||||
|
163
src/components/TotalStakeGraph.js
Normal file
163
src/components/TotalStakeGraph.js
Normal 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
164
src/pages/graph.js
Normal 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;
|
@ -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">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user