Big layout update

This commit is contained in:
Marco van Dijk 2022-03-08 17:02:15 +01:00
parent 1db2a934a2
commit fcdc274014
13 changed files with 489 additions and 298 deletions

View File

@ -7,6 +7,9 @@ const Block = (obj) => {
const [thisDate, thisTime] = dateObj.toISOString().split('T');
return (
<div className="rowAlignLeft" style={{ width: '100%', marginTop: '1em' }}>
<a className="selectOrch" href={obj.url}>
<img alt="" src="arb.svg" width="30em" height="30em" />
</a>
<span className="rowAlignRight elipsText">
<a className="selectOrch" href={"https://arbiscan.io/block/" + obj.block}>
🔗{obj.block}

View File

@ -3,9 +3,9 @@ import ReactTooltip from "react-tooltip";
const Address = (obj) => {
return (
<div className="rowAlignLeft" style={{ width: 'unset' }}>
<div className="rowAlignLeft" style={{ width: 'unset', margin: 0 }}>
<a className="selectOrchLight" href={"https://explorer.livepeer.org/accounts/" + obj.address} data-tip data-for={obj.seed} >
<div className="rowAlignLeft" style={{ width: 'unset' }}>
<div className="rowAlignLeft" style={{ width: 'unset', margin: 0 }}>
<img alt="" src="livepeer.png" width="20" height="20" />
<span className="elipsText">{obj.address}</span>
</div>

View File

@ -0,0 +1,47 @@
import React from "react";
import ScrollContainer from "react-indiana-drag-scroll";
import Address from "./OrchAddressViewer";
const OrchDelegatorViewer = (obj) => {
let delegators = obj.delegators;
if (delegators && delegators.length) {
return (
<div className="row" style={{ width: 'unset', marginBottom: '1em', marginTop: '1em' }}>
<div className="strokeSmollLeft" style={{ display: "flex" }}>
<div className="row" style={{ margin: '0' }}>
<h3 style={{ padding: 0, margin: 0 }}>{delegators.length} Current Delegators</h3>
</div>
<div className="content-wrapper">
<ScrollContainer className="overflow-container" hideScrollbars={false}>
<div className="overflow-content" style={{ cursor: 'grab', padding: 0, maxHeight: '300px' }}>
{
delegators.map((delObj, idx) => {
return (
<div className={obj.forceVertical ? "flexContainer forceWrap" : "flexContainer"} key={"delegator" + idx} style={{ margin: 0, textAlign: 'center',alignItems: 'center', justifyContent:'center' }}>
<Address address={delObj.id} seed={"delegator" + idx + delObj.id} />
<div className="rowAlignRight" style={{ margin: 0 }}>
<p className="darkText">{parseFloat(delObj.bondedAmount).toFixed(2)} LPT since round {delObj.startRound}</p>
</div>
</div>
)
})
}
</div>
</ScrollContainer>
</div>
</div>
</div>
)
}
return (
<div className="row" style={{ width: 'unset', marginBottom: '1em', marginTop: '1em' }}>
<div className="strokeSmollLeft" style={{ display: "flex" }}>
<div className="row" style={{ margin: '0' }}>
<h3 style={{ padding: 0, margin: 0 }}>The selected Orchestrator has no Delegators</h3>
</div>
</div>
</div>
)
}
export default OrchDelegatorViewer;

115
src/OrchInfoViewer.js Normal file
View File

@ -0,0 +1,115 @@
import React from "react";
import Stat from "./statViewer";
import ReactTooltip from "react-tooltip";
import Address from "./OrchAddressViewer";
function updateClipboard(newClip) {
navigator.clipboard.writeText(newClip).then(
() => {
console.log("Copied!");
},
() => {
console.log("Copy failed!");
}
);
}
function copyLink(addr) {
navigator.permissions
.query({ name: "clipboard-write" })
.then((result) => {
if (result.state === "granted" || result.state === "prompt") {
updateClipboard(addr);
}
});
}
const OrchInfoViewer = (obj) => {
let rewardCut = 0;
let feeCut = 0;
let totalStake = 0;
let totalVolumeETH = 0;
let totalVolumeUSD = 0;
let selfStake = 0;
let selfStakeRatio = 0;
let thisUrl = "";
let thisID = "";
if (obj.totalStake && obj.totalStake > 0) {
if (obj.rewardCut) {
rewardCut = (obj.rewardCut / 10000).toFixed(2);
}
if (obj.feeShare) {
feeCut = (100 - (obj.feeShare / 10000)).toFixed(2);
}
if (obj.totalStake) {
totalStake = parseFloat(obj.totalStake).toFixed(2);
}
if (obj.totalVolumeETH) {
totalVolumeETH = parseFloat(obj.totalVolumeETH * 1).toFixed(4);
}
if (obj.totalVolumeUSD) {
totalVolumeUSD = parseFloat(obj.totalVolumeUSD * 1).toFixed(2);
}
if (obj.delegator) {
selfStake = parseFloat(obj.delegator.bondedAmount);
selfStakeRatio = ((selfStake / totalStake) * 100).toFixed(2);
selfStake = selfStake.toFixed(2);
thisID = obj.delegator.id;
thisUrl = "https://explorer.livepeer.org/accounts/" + thisID;
}
let shareUrl;
if (obj.rootOnly) {
shareUrl = window.location.href;
} else {
let thisFullPath = window.location.href;
if (thisFullPath.lastIndexOf("?") > -1) {
thisFullPath = thisFullPath.substring(0, thisFullPath.lastIndexOf("?"));
}
shareUrl = thisFullPath + "?orchAddr=" + thisID;
}
return (
<div className="row" style={{ width: 'unset', }}>
<div className="strokeSmollLeft" style={{ display: "flex" }}>
<div style={{ flexDirection: 'row', display: "flex", borderBottom: '2px solid rgba(15,15,15,0.05)', marginTop: '1em' }}>
<a href={thisUrl}>
<h3 style={{ padding: 0, margin: 0 }}>Orchestrator Info</h3>
<Address address={thisID} />
</a>
</div>
<div className="row" style={{ margin: 0 }}>
<Stat header={"Earned Fees"} content1={totalVolumeETH + " Eth"} content2={"$" + totalVolumeUSD} />
</div>
<div className="row" style={{ margin: 0 }}>
<Stat header={"Commission"} title1={"Reward"} content1={rewardCut + "%"} title2={"Fee"} content2={feeCut + "%"} />
</div>
<div className="row" style={{ margin: 0 }}>
<Stat header={"Stake"} title1={"Total"} content1={totalStake + " LPT"} title2={"Self"} content2={selfStake + " LPT (" + selfStakeRatio + ")%"} />
</div>
<div className="strokeSmollLeft" style={{ display: "flex" }}>
<button style={{ marginBottom: '1em' }} className="selectOrchLight" data-tip data-for="registerTip" onClick={() => {
copyLink(shareUrl);
}}>
<img alt="" src="clipboard.svg" width="20em" height="20em" />
</button>
<ReactTooltip id="registerTip" place="top" effect="solid">
Copy to clipboard
</ReactTooltip>
</div>
</div>
</div>
)
}
return (
<div className="row" style={{ width: 'unset', marginBottom: '1em', marginTop: '1em' }}>
<div className="strokeSmollLeft" style={{ display: "flex" }}>
<div className="row" style={{ margin: '0' }}>
<h3 style={{ padding: 0, margin: 0 }}>The selected Orchestrator is currently inactive</h3>
</div>
</div>
</div>
)
}
export default OrchInfoViewer;

View File

@ -17,6 +17,7 @@ export const RECEIVE_BLOCKCHAIN_DATA = "RECEIVE_BLOCKCHAIN_DATA";
export const RECEIVE_EVENTS = "RECEIVE_EVENTS";
export const RECEIVE_CURRENT_ORCHESTRATOR = "RECEIVE_CURRENT_ORCHESTRATOR";
export const RECEIVE_ORCHESTRATOR = "RECEIVE_ORCHESTRATOR";
export const CLEAR_ORCHESTRATOR = "CLEAR_ORCHESTRATOR";
const setQuotes = message => ({
type: RECEIVE_QUOTES, message
@ -33,6 +34,9 @@ const setCurrentOrchestratorInfo = message => ({
const setOrchestratorInfo = message => ({
type: RECEIVE_ORCHESTRATOR, message
});
const clearOrchestratorInfo = () => ({
type: CLEAR_ORCHESTRATOR
})
export const getQuotes = () => async dispatch => {
const response = await apiUtil.getQuotes();
@ -127,7 +131,7 @@ export const getEvents = () => async dispatch => {
else if (eventContainsRebond) {
eventType = "Stake";
eventColour = stakeColour;
eventDescription = "increased their stake to " + tmpAmount.toFixed(2) + " LPT at";
eventDescription = "is now staking " + tmpAmount.toFixed(2) + " LPT";
}
// Fill description of Stake Event if it wasn't set yet
@ -136,7 +140,7 @@ export const getEvents = () => async dispatch => {
eventDescription = "staked " + tmpAmount.toFixed(2) + " LPT";
} else if (eventFrom === eventTo) {
eventFrom = "";
eventDescription = "increased their stake to " + tmpAmount.toFixed(2) + " LPT";
eventDescription = "is now staking " + tmpAmount.toFixed(2) + " LPT";
} else {
eventDescription = "moved a " + tmpAmount.toFixed(2) + " LPT stake";
}
@ -369,4 +373,8 @@ export const getOrchestratorInfo = (orchAddr) => async dispatch => {
}
}
return dispatch(receiveErrors(data));
};
export const clearOrchestrator = () => async dispatch => {
return dispatch(clearOrchestratorInfo({}));
};

View File

@ -10,7 +10,7 @@ import Block from "./BlockViewer";
const EventButton = (obj) => {
const dispatch = useDispatch();
let eventArrow;
let eventTo;
let eventFrom;
let eventCaller;
@ -21,13 +21,14 @@ const EventButton = (obj) => {
if (obj.eventObj.eventTo === "0x0000000000000000000000000000000000000000") {
obj.eventObj.eventTo = "";
}
if (obj.eventObj.eventTo !== "" || obj.eventObj.eventFrom !== "") {
eventArrow = <p style={{marginRight: 0}}></p>;
}
if (obj.eventObj.eventTo || obj.eventObj.eventFrom || obj.eventObj.eventCaller) {
if (obj.eventObj.eventTo) {
eventTo =
<div className="rowAlignRight" style={{ width: 'unset', marginLeft: 0 }}>
<div className="rowAlignLeft" style={{ width: '100%', margin: 0, marginLeft: '0.5em' }}>
<p>To</p>
<a className="selectOrch" href={"https://explorer.livepeer.org/accounts/" + obj.eventObj.eventTo}>
<img alt="" src="livepeer.png" width="20em" height="20em" style={{ margin: 0 }} />
</a>
<button className="selectOrch" style={{ margin: 0, padding: '0.5em' }} onClick={() => { obj.setSearchTerm(obj.eventObj.eventTo) }} >
<span className="elipsText">🔎</span>
</button>
@ -38,7 +39,11 @@ const EventButton = (obj) => {
}
if (obj.eventObj.eventFrom) {
eventFrom =
<div className="rowAlignRight" style={{ width: 'unset', margin: 0 }}>
<div className="rowAlignLeft" style={{ width: '100%', margin: 0, marginLeft: '0.5em' }}>
<p>From</p>
<a className="selectOrch" href={"https://explorer.livepeer.org/accounts/" + obj.eventObj.eventFrom}>
<img alt="" src="livepeer.png" width="20em" height="20em" style={{ margin: 0 }} />
</a>
<button className="selectOrch" style={{ margin: 0, padding: '0.5em' }} onClick={() => { obj.setSearchTerm(obj.eventObj.eventFrom) }} >
<span className="elipsText">🔎</span>
</button>
@ -49,7 +54,11 @@ const EventButton = (obj) => {
}
if (obj.eventObj.eventCaller) {
eventCaller =
<div className="rowAlignLeft" style={{ width: 'unset', margin: 0 }}>
<div className="rowAlignLeft" style={{ width: '100%', margin: 0, marginLeft: '0.5em' }}>
<p>Caller</p>
<a className="selectOrch" href={"https://explorer.livepeer.org/accounts/" + obj.eventObj.eventCaller}>
<img alt="" src="livepeer.png" width="20em" height="20em" style={{ margin: 0 }} />
</a>
<button className="selectOrch" style={{ margin: 0, padding: '0.5em' }} onClick={() => { obj.setSearchTerm(obj.eventObj.eventCaller) }} >
<span className="elipsText">🔎</span>
</button>
@ -58,40 +67,29 @@ const EventButton = (obj) => {
</button>
</div>
}
eventRightAddr = <div className="rowAlignRight" style={{ width: 'unset', padding: 0, margin: 0 }}>
{eventFrom}
{eventArrow}
{eventTo}
eventRightAddr = <div className="strokeSmollLeft" style={{ width: 'unset', padding: 0, margin: 0 }}>
</div>
}
let blockNumber;
if (obj.isFirstOfBlock) {
blockNumber = <Block block={obj.isFirstOfBlock} time={obj.time} />
blockNumber = <Block block={obj.isFirstOfBlock} time={obj.time} url={obj.eventObj.transactionUrl} />
}
return (
<div className="stroke" style={{ width: '100%', padding: 0, margin: 0 }}>
{blockNumber}
<div className="rowAlignLeft" style={{ backgroundColor: obj.eventObj.eventColour, borderRadius: "1.2em", width: '100%' }}>
<div className="rowAlignLeft" style={{ flex: '1', width: 'unset' }}>
<a className="selectOrch" href={obj.eventObj.transactionUrl}>
<img alt="" src="arb.svg" width="30" height="30" />
</a>
<a className="selectOrch" href={"https://explorer.livepeer.org/accounts/" + obj.eventObj.eventCaller}>
<img alt="" src="livepeer.png" width="30" height="30" />
</a>
<div className="rowAlignLeft" style={{ flex: '1', width: '100%', padding: 0, margin: 0 }}>
{eventCaller}
</div>
<div className="rowAlignLeft" style={{ borderRadius: "1.2em", backgroundColor: obj.eventObj.eventColour, padding: 0, margin: 0 }}>
<div className="strokeSmollLeft">
{eventCaller}
<p className="rowAlignLeft withWrap" style={{ width: '100%' }}>
💬 {obj.eventObj.eventDescription}
</p>
{eventFrom}
{eventTo}
</div>
<div className="rowAlignLeft" style={{ flex: '2', width: 'unset', padding: 0, margin: 0 }}>
<span className="rowAlignLeft elipsText">
{obj.eventObj.eventDescription}
</span>
{eventRightAddr}
</div>
</div >
</div>
</div>
)
}

View File

@ -17,7 +17,6 @@ const defaultMaxShown = 100;
const defaultIncrementMaxShown = 100;
const EventViewer = (obj) => {
const [searchTerm, setSearchTerm] = useState(obj.prefill || "");
const [amountFilter, setAmountFilter] = useState("0");
const [maxAmount, setMaxAmount] = useState(defaultMaxShown);
const [filterActivated, setFilterActivated] = useState(true);
@ -49,35 +48,29 @@ const EventViewer = (obj) => {
let prevBlock = 0;
let showMoreBlock;
if (maxAmount < limitShown) {
showMoreBlock = <div className="strokeSmollLeft" style={{ margin: 0, padding: 0 }}>
<h3></h3>
<button className={"nonHomeButton"} style={{ backgroundColor: greyColour, margin: 0, padding: '0', width: '5em' }} onClick={() => {
setMaxAmount(maxAmount + defaultIncrementMaxShown);
}}>
<h3>Show more</h3>
</button>
</div>
showMoreBlock = <button className={"nonHomeButton"} style={{ backgroundColor: greyColour, margin: 0, padding: '0', width: '5em' }} onClick={() => {
setMaxAmount(maxAmount + defaultIncrementMaxShown);
}}>
<h3>Show more</h3>
</button>
}
let showLessBlock;
if (defaultMaxShown < maxAmount) {
showLessBlock = <div className="strokeSmollLeft" style={{ margin: 0, padding: 0 }}>
<h3></h3>
<button className={"nonHomeButton"} style={{ backgroundColor: greyColour, margin: 0, padding: '0', width: '5em' }} onClick={() => {
setMaxAmount(defaultMaxShown);
}}>
<h3>Show {defaultMaxShown}</h3>
</button>
</div>
showLessBlock = <button className={"nonHomeButton"} style={{ backgroundColor: greyColour, margin: 0, padding: '0', width: '5em' }} onClick={() => {
setMaxAmount(defaultMaxShown);
}}>
<h3>Show {defaultMaxShown}</h3>
</button>
} else {
showLessBlock = <div className="strokeSmollLeft" style={{ margin: 0, padding: 0, width: '5em' }}></div>
}
let searchTermText;
if (searchTerm !== "") {
if (searchTerm.length > 15) {
searchTermText = <h3>Only showing addresses containing {searchTerm.substring(0, 15)}...</h3>
if (obj.searchTerm !== "") {
if (obj.searchTerm.length > 15) {
searchTermText = <h3>Only showing addresses containing {obj.searchTerm.substring(0, 15)}...</h3>
} else {
searchTermText = <h3>Only showing addresses containing {searchTerm}</h3>
searchTermText = <h3>Only showing addresses containing {obj.searchTerm}</h3>
}
} else {
searchTermText = <h3>Filter by Orchestrator address</h3>
@ -95,11 +88,11 @@ const EventViewer = (obj) => {
}
}
// Filter name on from, to, caller
if (searchTerm !== "") {
if (obj.searchTerm !== "") {
let isFiltered = true;
if (eventObj.eventCaller.toLowerCase().includes(searchTerm.toLowerCase())) isFiltered = false;
if (eventObj.eventFrom.toLowerCase().includes(searchTerm.toLowerCase())) isFiltered = false;
if (eventObj.eventTo.toLowerCase().includes(searchTerm.toLowerCase())) isFiltered = false;
if (eventObj.eventCaller.toLowerCase().includes(obj.searchTerm.toLowerCase())) isFiltered = false;
if (eventObj.eventFrom.toLowerCase().includes(obj.searchTerm.toLowerCase())) isFiltered = false;
if (eventObj.eventTo.toLowerCase().includes(obj.searchTerm.toLowerCase())) isFiltered = false;
if (isFiltered) continue;
}
// Filter Events on filter buttons
@ -164,7 +157,7 @@ const EventViewer = (obj) => {
eventList.push(<EventButton
key={eventObj.transactionHash + unfiltered}
eventObj={eventObj}
setSearchTerm={setSearchTerm}
setSearchTerm={obj.setSearchTerm}
/>);
} else {
prevBlock = eventObj.transactionBlock;
@ -173,81 +166,69 @@ const EventViewer = (obj) => {
eventObj={eventObj}
isFirstOfBlock={prevBlock}
time={eventObj.transactionTime}
setSearchTerm={setSearchTerm}
setSearchTerm={obj.setSearchTerm}
/>);
}
}
}
return (
<div className="stroke roundedOpaque" style={{ padding: 0, margin: 0, marginTop: '2em', position: 'absolute', bottom: 0, top: '300px', overflowY: 'auto', overflowX: 'hidden', width: 'unset' }}>
<div className="row showNeverOnMobile">
<div className="row">
{showLessBlock}
let filterBit;
if (obj.showFilter) {
filterBit = <div className="row roundedOpaque" style={{ borderTopLeftRadius: 0, borderTopRightRadius: 0, borderBottomLeftRadius: 0, margin: 0 }}>
<div className="strokeSmollLeft" style={{ margin: 0, padding: 0, flex: 1 }}>
<div className="stroke" style={{ margin: "0", padding: 0 }}>
<div className="strokeSmollLeft" style={{ margin: 0, padding: 0 }}>
<h3>Showing max {maxAmount} results</h3>
</div>
{showMoreBlock}
</div>
<div className="row">
<div className="stroke" style={{ margin: "0", padding: 0 }}>
{searchTermText}
<input className="searchField" style={{ width: '100%' }}
value={searchTerm}
onChange={(evt) => setSearchTerm(evt.target.value)}
placeholder='Filter by Orchestrator address'
type="text"
/>
<div className="row" style={{ margin: 0, padding: 0 }}>
<div className="strokeSmollLeft" style={{ margin: 0, padding: 0 }}>
{showLessBlock}
</div>
<div className="strokeSmollLeft" style={{ margin: 0, padding: 0 }}>
{showMoreBlock}
</div>
</div>
</div>
<div className="row">
</div>
<div className="strokeSmollLeft" style={{ margin: 0, padding: 0, flex: 2 }}>
<div className="row" style={{ margin: 0, padding: 0 }}>
{searchTermText}
</div>
<div className="row" style={{ margin: 0, padding: 0 }}>
<input className="searchField" style={{ width: '80%', paddingLeft: '1em', paddingRight: '1em' }}
value={obj.searchTerm}
onChange={(evt) => obj.setSearchTerm(evt.target.value)}
placeholder='Filter by Orchestrator address'
type="text"
/>
<div className="strokeSmollLeft" style={{ margin: 0, padding: 0 }}>
<button className={"nonHomeButton"} style={{ backgroundColor: greyColour, margin: 0, padding: '0', width: '6em' }} onClick={() => {
obj.setSearchTerm("");
}}>
<h3>Clear</h3>
</button>
</div>
</div>
</div>
<div className="strokeSmollLeft" style={{ margin: 0, padding: 0, flex: 2 }}>
<div className="row" style={{ margin: 0, padding: 0 }}>
<h3>{parseFloat(amountFilter) > 0 ? ("Only showing higher than " + amountFilter) : "Filter by minimum value"}</h3>
</div>
<div className="row" style={{ margin: 0, padding: 0 }}>
<div className="strokeSmollLeft" style={{ margin: 0, padding: 0 }}>
<h3></h3>
<button className={"nonHomeButton"} style={{ backgroundColor: greyColour, margin: 0, padding: '0', width: '5em' }} onClick={() => {
const curVal = parseFloat(amountFilter);
setAmountFilter(curVal - 1000);
setAmountFilter(0);
}}>
<h3>-1000</h3>
<h3>0</h3>
</button>
</div>
<input className="searchField" style={{ margin: 0, padding: 0, height: '2em', width: '80%', paddingLeft: '1em', paddingRight: '1em' }}
value={amountFilter}
onChange={(evt) => setAmountFilter(evt.target.value)}
placeholder='Filter by minimum value'
type="number"
/>
<div className="strokeSmollLeft" style={{ margin: 0, padding: 0 }}>
<h3></h3>
<button className={"nonHomeButton"} style={{ backgroundColor: greyColour, margin: 0, padding: '0', width: '4em' }} onClick={() => {
const curVal = parseFloat(amountFilter);
setAmountFilter(curVal - 100);
}}>
<h3>-100</h3>
</button>
</div>
<div className="strokeSmollLeft" style={{ margin: 0, padding: 0 }}>
<h3></h3>
<button className={"nonHomeButton"} style={{ backgroundColor: greyColour, margin: 0, padding: '0', width: '3em' }} onClick={() => {
const curVal = parseFloat(amountFilter);
setAmountFilter(curVal - 10);
}}>
<h3>-10</h3>
</button>
</div>
<div className="strokeSmollLeft" style={{ margin: "0", padding: 0 }}>
<h3>{parseFloat(amountFilter) > 0 ? ("Only showing higher than " + amountFilter) : "Filter by minimum value"}</h3>
<input className="searchField" style={{ margin: 0, padding: 0, height: '2em', width: '80%', paddingLeft: '1em', paddingRight: '1em' }}
value={amountFilter}
onChange={(evt) => setAmountFilter(evt.target.value)}
placeholder='Filter by minimum value'
type="number"
/>
</div>
<div className="strokeSmollLeft" style={{ margin: 0, padding: 0 }}>
<h3></h3>
<button className={"nonHomeButton"} style={{ backgroundColor: greyColour, margin: 0, padding: '0', width: '3em' }} onClick={() => {
const curVal = parseFloat(amountFilter);
setAmountFilter(curVal + 10);
}}>
<h3>+10</h3>
</button>
</div>
<div className="strokeSmollLeft" style={{ margin: 0, padding: 0 }}>
<h3></h3>
<button className={"nonHomeButton"} style={{ backgroundColor: greyColour, margin: 0, padding: '0', width: '4em' }} onClick={() => {
const curVal = parseFloat(amountFilter);
setAmountFilter(curVal + 100);
@ -256,7 +237,6 @@ const EventViewer = (obj) => {
</button>
</div>
<div className="strokeSmollLeft" style={{ margin: 0, padding: 0 }}>
<h3></h3>
<button className={"nonHomeButton"} style={{ backgroundColor: greyColour, margin: 0, padding: '0', width: '5em' }} onClick={() => {
const curVal = parseFloat(amountFilter);
setAmountFilter(curVal + 1000);
@ -266,48 +246,60 @@ const EventViewer = (obj) => {
</div>
</div>
</div>
<div className="content-wrapper" style={{ alignItems: 'stretch', width: '100%' }}>
<ScrollContainer className="overflow-container" hideScrollbars={false} style={{}}>
<div className="overflow-content" style={{ cursor: 'grab', paddingTop: 0}}>
{eventList}
</div>
}
return (
<div className="strokeSmollLeft" style={{ padding: 0, margin: 0, height: 'calc( 100vh - 50px)' }}>
{filterBit}
<div className="row" style={{ padding: 0, margin: 0, width: '100%', height: '100%' }}>
<div className="stroke roundedOpaque" style={{ padding: 0, margin: 0, width: 'unset', height: '100%', marginRight: '1em', overflow: 'hidden', marginTop: '1em', overflowX: 'scroll' }}>
<div className="content-wrapper" style={{ width: '100%' }}>
<ScrollContainer className="overflow-container" hideScrollbars={false}>
<div className="overflow-content" style={{ cursor: 'grab', paddingTop: 0 }}>
<div className={obj.forceVertical ? "flexContainer forceWrap" : "flexContainer"} style={{ margin: 0, textAlign: 'center', alignItems: 'center', justifyContent: 'center' }}>
{eventList}
</div>
</div>
</ScrollContainer>
<div className="strokeSmollLeft" style={{ marginRight: "1em" }}>
<button className={filterActivated ? "row nonHomeButton active" : "row nonHomeButton"} style={{ backgroundColor: filterActivatedColour }} onClick={() => {
setFilterActivated(!filterActivated);
}}>
<h3>Activated</h3>
</button>
<button className={rewardActivated ? "row nonHomeButton active" : "row nonHomeButton"} style={{ backgroundColor: rewardActivatedColour }} onClick={() => {
setRewardActivated(!rewardActivated);
}}>
<h3>Reward</h3>
</button>
<button className={updateActivated ? "row nonHomeButton active" : "row nonHomeButton"} style={{ backgroundColor: updateActivatedColour }} onClick={() => {
setUpdateActivated(!updateActivated);
}}>
<h3>Update</h3>
</button>
<button className={withdrawActivated ? "row nonHomeButton active" : "row nonHomeButton"} style={{ backgroundColor: withdrawActivatedColour }} onClick={() => {
setWithdrawActivated(!withdrawActivated);
}}>
<h3>Withdraw</h3>
</button>
<button className={stakeActivated ? "row nonHomeButton active" : "row nonHomeButton"} style={{ backgroundColor: stakeActivatedColour }} onClick={() => {
setStakeActivated(!stakeActivated);
}}>
<h3>Stake</h3>
</button>
<button className={delegatorRewardActivated ? "row nonHomeButton active" : "row nonHomeButton"} style={{ backgroundColor: delegatorActivatedColour }} onClick={() => {
setDelegatorRewardActivated(!delegatorRewardActivated);
}}>
<h3>Claim</h3>
</button>
<button className={unbondActivated ? "row nonHomeButton active" : "row nonHomeButton"} style={{ backgroundColor: unbondActivatedColour }} onClick={() => {
setUnbondActivated(!unbondActivated);
}}>
<h3>Unbond</h3>
</button>
</div>
</div>
</ScrollContainer>
<div className="strokeSmollLeft" style={{ marginRight: "1em" }}>
<button className={filterActivated ? "row nonHomeButton active" : "row nonHomeButton"} style={{ backgroundColor: filterActivatedColour }} onClick={() => {
setFilterActivated(!filterActivated);
}}>
<h3>Activated</h3>
</button>
<button className={rewardActivated ? "row nonHomeButton active" : "row nonHomeButton"} style={{ backgroundColor: rewardActivatedColour }} onClick={() => {
setRewardActivated(!rewardActivated);
}}>
<h3>Reward</h3>
</button>
<button className={updateActivated ? "row nonHomeButton active" : "row nonHomeButton"} style={{ backgroundColor: updateActivatedColour }} onClick={() => {
setUpdateActivated(!updateActivated);
}}>
<h3>Update</h3>
</button>
<button className={withdrawActivated ? "row nonHomeButton active" : "row nonHomeButton"} style={{ backgroundColor: withdrawActivatedColour }} onClick={() => {
setWithdrawActivated(!withdrawActivated);
}}>
<h3>Withdraw</h3>
</button>
<button className={stakeActivated ? "row nonHomeButton active" : "row nonHomeButton"} style={{ backgroundColor: stakeActivatedColour }} onClick={() => {
setStakeActivated(!stakeActivated);
}}>
<h3>Stake</h3>
</button>
<button className={delegatorRewardActivated ? "row nonHomeButton active" : "row nonHomeButton"} style={{ backgroundColor: delegatorActivatedColour }} onClick={() => {
setDelegatorRewardActivated(!delegatorRewardActivated);
}}>
<h3>Claim</h3>
</button>
<button className={unbondActivated ? "row nonHomeButton active" : "row nonHomeButton"} style={{ backgroundColor: unbondActivatedColour }} onClick={() => {
setUnbondActivated(!unbondActivated);
}}>
<h3>Unbond</h3>
</button>
</div>
</div>
</div>

View File

@ -44,7 +44,7 @@ const Grafana = (obj) => {
<div className="flexContainer">
<div className="stroke" style={{ marginTop: 0, marginBottom: 5, paddingBottom: 0 }}>
<div className="stroke roundedOpaque" style={{}}>
<div className="row">
<div className="flexContainer" style={{ margin: 0, textAlign: 'center',alignItems: 'center', justifyContent:'center' }}>
<div className="row">
<img alt="" src="livepeer.png" width="30" height="30" />
<p>${lptPrice}</p>

View File

@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'
import './style.css';
import { Navigate, useSearchParams } from "react-router-dom";
import { useSelector, useDispatch } from 'react-redux'
import { getOrchestratorInfo } from "./actions/livepeer";
import { getOrchestratorInfo, clearOrchestrator } from "./actions/livepeer";
import EventViewer from "./eventViewer";
import Orchestrator from "./orchestratorViewer";
import Stat from "./statViewer";
@ -11,14 +11,18 @@ import Stat from "./statViewer";
const Livepeer = (obj) => {
const [prefill, setPrefill] = useSearchParams();
const dispatch = useDispatch();
const [searchTerm, setSearchTerm] = useState("");
const dispatch = useDispatch();
const livepeer = useSelector((state) => state.livepeerstate);
const [redirectToHome, setRedirectToHome] = useState(false);
const [showFilter, setShowFilter] = useState(false);
const [showSidebar, setShowSidebar] = useState(true);
console.log("Rendering Livepeer");
useEffect(() => {
if (prefill.get('orchAddr') && prefill.get('orchAddr') !== "") {
dispatch(getOrchestratorInfo(prefill.get('orchAddr')));
setSearchTerm(prefill.get('orchAddr'));
}
}, [prefill]);
@ -123,36 +127,72 @@ const Livepeer = (obj) => {
}
let thisOrchObj;
let headerString;
if (livepeer.selectedOrchestrator) {
thisOrchObj = livepeer.selectedOrchestrator;
headerString = "Inspecting " + thisOrchObj.id;
} else {
headerString = "Livepeer Orchestrator Explorer";
}
let sidebar;
if (showSidebar){
sidebar = <div id='sideContent'>
<div className="strokeSmollLeft" style={{ margin: 0, padding: 0, width: '100%', marginTop: '1em' }}>
<div className="row" style={{ alignItems: 'stretch', height: '100%', padding: '0.2em', width: "unset" }}>
<Orchestrator thisOrchestrator={thisOrchObj} rootOnly={false} forceVertical={true} />
</div>
<div className="stroke metaSidebar" style={{ padding: 0, maxWidth: "300px" }}>
<div className="row" style={{ margin: 0, padding: 0 }}>
<h3 style={{ margin: 0, padding: 0 }}>Smart contract prices</h3>
</div>
<div className="stroke" style={{ margin: 0, padding: 0 }}>
<Stat header={"Reward Call"} content1={"$" + redeemRewardCostL2USD + " (vs " + redeemRewardCostL1USD + " on L1)"} />
<Stat header={"Claim Ticket"} content1={"$" + claimTicketCostL2USD + " (vs " + claimTicketCostL1USD + " on L1)"} />
<Stat header={"Staking Fees"} content1={"$" + stakeFeeCostL2USD + " (vs " + stakeFeeCostL1USD + " on L1)"} />
<Stat header={"Change Commission"} content1={"$" + commissionFeeCostL2USD + " (vs " + commissionFeeCostL1USD + " on L1)"} />
<Stat header={"Change URI"} content1={"$" + serviceUriFeeCostL2USD + " (vs " + serviceUriFeeCostL1USD + " on L1)"} />
</div>
</div>
</div>
</div >
}
return (
<div style={{ width: '100%', height: '100%' }}>
<div className="row" style={{ margin: 0, padding: 0, backgroundColor: "rgba(180, 175, 252, 0.80)", boxSizing: "border-box", backdropDilter: "blur(6px)", boxSshadow: "9px 13px 18px 8px rgba(8, 7, 56, 0.692)", position: 'absolute', top: '0px', left: '0px', height: '300px', right: '0px', overflow: 'hidden' }}>
<button className="homeButton" onClick={() => {
setRedirectToHome(true);
}}>
<img alt="" src="livepeer.png" width="100em" height="100em" />
</button>
<div className="row" style={{ alignItems: 'stretch', height: '100%', flex: 2, padding: '0.2em', maxWidth: "1100px" }}>
<Orchestrator thisOrchestrator={thisOrchObj} rootOnly={false} />
<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 style={{margin: 0, padding: 0}}>🏠</h1>
</button>
<h4>{headerString}</h4>
</div>
<div className="stroke metaSidebar showNeverOnMobile" style={{ padding: 0, maxWidth: "300px" }}>
<div className="row" style={{ margin: 0, padding: 0 }}>
<h3 style={{ margin: 0, padding: 0 }}>Smart contract prices</h3>
</div>
<div className="stroke" style={{ margin: 0, padding: 0 }}>
<Stat header={"Reward Call"} content1={"$" + redeemRewardCostL2USD + " (vs " + redeemRewardCostL1USD + " on L1)"} />
<Stat header={"Claim Ticket"} content1={"$" + claimTicketCostL2USD + " (vs " + claimTicketCostL1USD + " on L1)"} />
<Stat header={"Staking Fees"} content1={"$" + stakeFeeCostL2USD + " (vs " + stakeFeeCostL1USD + " on L1)"} />
<Stat header={"Change Commission"} content1={"$" + commissionFeeCostL2USD + " (vs " + commissionFeeCostL1USD + " on L1)"} />
<Stat header={"Change URI"} content1={"$" + serviceUriFeeCostL2USD + " (vs " + serviceUriFeeCostL1USD + " on L1)"} />
</div>
<div className='rowAlignRight'>
<button className="homeButton" style={{padding: 0, paddingRight: '1em', paddingLeft: '1em'}} onClick={() => {
dispatch(clearOrchestrator());
setSearchTerm("");
}}>
<h4> Clear</h4>
</button>
<button className="homeButton" style={{padding: 0, paddingRight: '1em', paddingLeft: '1em'}} onClick={() => {
setShowSidebar(!showSidebar);
}}>
<h4>🔎 Sidebar</h4>
</button>
<button className="homeButton" style={{padding: 0, paddingRight: '1em', paddingLeft: '1em'}} onClick={() => {
setShowFilter(!showFilter);
}}>
<h4>🛠 Filter</h4>
</button>
</div>
</div>
<div id='bodyContent'>
{sidebar}
<div className="mainContent">
<EventViewer events={eventsList} searchTerm={searchTerm} setSearchTerm={setSearchTerm} forceVertical={true} showFilter={showFilter} />
</div>
</div >
<div className="row" style={{ margin: 0, padding: 0 }}>
<EventViewer events={eventsList} prefill={prefill.get('orchAddr')} />
</div>
</div >
);

View File

@ -3,6 +3,8 @@ import ScrollContainer from "react-indiana-drag-scroll";
import Stat from "./statViewer";
import ReactTooltip from "react-tooltip";
import Address from "./OrchAddressViewer";
import OrchDelegatorViewer from "./OrchDelegatorViewer";
import OrchInfoViewer from "./OrchInfoViewer";
function updateClipboard(newClip) {
navigator.clipboard.writeText(newClip).then(
@ -26,118 +28,71 @@ function copyLink(addr) {
}
const Orchestrator = (obj) => {
let rewardCut = 0;
let feeCut = 0;
let totalStake = 0;
let totalVolumeETH = 0;
let totalVolumeUSD = 0;
let delegators = [];
let selfStake = 0;
let selfStakeRatio = 0;
let thisUrl = "";
let thisID = "";
if (obj.thisOrchestrator) {
if (obj.thisOrchestrator.rewardCut) {
rewardCut = (obj.thisOrchestrator.rewardCut / 10000).toFixed(2);
}
if (obj.thisOrchestrator.feeShare) {
feeCut = (100 - (obj.thisOrchestrator.feeShare / 10000)).toFixed(2);
}
if (obj.thisOrchestrator.totalStake) {
totalStake = parseFloat(obj.thisOrchestrator.totalStake).toFixed(2);
}
if (obj.thisOrchestrator.totalVolumeETH) {
totalVolumeETH = parseFloat(obj.thisOrchestrator.totalVolumeETH * 1).toFixed(4);
}
if (obj.thisOrchestrator.totalVolumeUSD) {
totalVolumeUSD = parseFloat(obj.thisOrchestrator.totalVolumeUSD * 1).toFixed(2);
}
if (obj.thisOrchestrator.delegators && obj.thisOrchestrator.delegator) {
delegators = obj.thisOrchestrator.delegators;
selfStake = parseFloat(obj.thisOrchestrator.delegator.bondedAmount);
selfStakeRatio = ((selfStake / totalStake) * 100).toFixed(2);
selfStake = selfStake.toFixed(2);
thisID = obj.thisOrchestrator.delegator.id;
thisUrl = "https://explorer.livepeer.org/accounts/" + thisID;
}
let shareUrl;
if (obj.rootOnly) {
shareUrl = window.location.href;
} else {
let thisFullPath = window.location.href;
if (thisFullPath.lastIndexOf("?") > -1) {
thisFullPath = thisFullPath.substring(0, thisFullPath.lastIndexOf("?"));
}
shareUrl = thisFullPath + "?orchAddr=" + thisID;
}
return (
<div className="hostInfo">
<div className="strokeSmollLeft" style={{ display: "flex" }}>
<div style={{ flexDirection: 'row', display: "flex" }} style={{ marginTop: '1em' }}>
<a href={thisUrl}>
<h3 style={{ padding: 0, margin: 0 }}>Orchestrator Info</h3>
<Address address={thisID} />
</a>
</div>
<div className="row" style={{}}>
<Stat header={"Earned Fees"} content1={totalVolumeETH + " Eth"} content2={"$" + totalVolumeUSD} />
</div>
<div className="row" style={{}}>
<Stat header={"Commission"} title1={"Reward"} content1={rewardCut + "%"} title2={"Fee"} content2={feeCut + "%"} />
</div>
<div className="row" style={{}}>
<Stat header={"Stake"} title1={"Total"} content1={totalStake + " LPT"} title2={"Self"} content2={selfStake + " LPT (" + selfStakeRatio + ")%"} />
</div>
<div className="strokeSmollLeft" style={{ display: "flex" }}>
<button style={{marginBottom:'1em'}} className="selectOrchLight" data-tip data-for="registerTip" onClick={() => {
copyLink(shareUrl);
}}>
<img alt="" src="clipboard.svg" width="20em" height="20em" />
</button>
<ReactTooltip id="registerTip" place="top" effect="solid">
Copy to clipboard
</ReactTooltip>
if (obj.forceVertical) {
return (
<div className="hostInfo">
<div className="flexContainer" style={{ justifyContent: 'flex-start', alignItems: 'flex-start', flexDirection: 'column' }}>
<OrchInfoViewer
rewardCut={obj.thisOrchestrator.rewardCut}
feeShare={obj.thisOrchestrator.feeShare}
totalStake={obj.thisOrchestrator.totalStake}
totalVolumeETH={obj.thisOrchestrator.totalVolumeETH}
totalVolumeUSD={obj.thisOrchestrator.totalVolumeUSD}
delegator={obj.thisOrchestrator.delegator}
/>
<OrchDelegatorViewer delegators={obj.thisOrchestrator.delegators} forceVertical={obj.forceVertical} />
</div>
</div>
<div className="strokeSmollLeft" style={{ alignItems: 'stretch', flex: 2, marginLeft: '1em', borderLeft: '3px solid rgba(15,15,15,0.05)' }}>
<div className="row" style={{ marginTop: '1em' }}>
<h3 style={{ padding: 0, margin: 0 }}>{delegators.length} Current Delegators</h3>
)
}else{
return (
<div className="hostInfo">
<div className="flexContainer" style={{ justifyContent: 'flex-start', alignItems: 'flex-start' }}>
<OrchInfoViewer
rewardCut={obj.thisOrchestrator.rewardCut}
feeShare={obj.thisOrchestrator.feeShare}
totalStake={obj.thisOrchestrator.totalStake}
totalVolumeETH={obj.thisOrchestrator.totalVolumeETH}
totalVolumeUSD={obj.thisOrchestrator.totalVolumeUSD}
delegator={obj.thisOrchestrator.delegator}
/>
<OrchDelegatorViewer delegators={obj.thisOrchestrator.delegators} forceVertical={obj.forceVertical} />
</div>
<div className="content-wrapper">
<ScrollContainer className="overflow-container" hideScrollbars={false}>
<div className="overflow-content" style={{ cursor: 'grab' }}>
{
delegators.map((delObj, idx) => {
return (
<div className="rowAlignLeft" key={"delegator" + idx} style={{ marginLeft: '1em', borderBottom: '2px solid rgba(15,15,15,0.05)' }}>
<Address address={delObj.id} seed={"delegator" + idx + delObj.id} />
<div className="rowAlignRight">
<p className="darkText">{parseFloat(delObj.bondedAmount).toFixed(2)} LPT since round {delObj.startRound}</p>
</div>
</div>
)
})
}
</div>
</ScrollContainer>
</div>
)
}
}
if (obj.forceVertical) {
return (
<div className="hostInfo">
<div className="flexContainer" style={{ alignItems: 'center', flexDirection: 'column' }}>
<div className="rowAlignLeft">
<img alt="" src="livepeer.png" width="30" height="30" />
<h3>Orchestrator Info</h3>
</div>
<div className="rowAlignLeft">
<p>Inspect an Orchestrator by clicking on their address</p>
</div>
</div>
</div>
)
} else {
return (
<div className="hostInfo">
<div className="flexContainer" style={{ alignItems: 'center' }}>
<div className="rowAlignLeft">
<img alt="" src="livepeer.png" width="30" height="30" />
<h3>Orchestrator Info</h3>
</div>
<div className="rowAlignLeft">
<p>Inspect an Orchestrator by clicking on their address</p>
</div>
</div>
</div>
)
}
return (
<div className="hostInfo">
<div className="rowAlignLeft">
<img alt="" src="livepeer.png" width="30" height="30" />
<h3>Orchestrator Info</h3>
</div>
<div className="rowAlignLeft">
<p>Click on an orchestrator address in the list below!</p>
</div>
</div>
)
}
export default Orchestrator;

View File

@ -3,7 +3,8 @@ import {
RECEIVE_BLOCKCHAIN_DATA,
RECEIVE_EVENTS,
RECEIVE_ORCHESTRATOR,
RECEIVE_CURRENT_ORCHESTRATOR
RECEIVE_CURRENT_ORCHESTRATOR,
CLEAR_ORCHESTRATOR
} from "../../actions/livepeer";
export default (state = {}, { type, message }) => {
@ -19,6 +20,8 @@ export default (state = {}, { type, message }) => {
return { ...state, thisOrchestrator: message };
case RECEIVE_ORCHESTRATOR:
return { ...state, selectedOrchestrator: message };
case CLEAR_ORCHESTRATOR:
return { ...state, selectedOrchestrator: null };
default:
return { ...state };
}

View File

@ -15,16 +15,16 @@ const Stat = (obj) => {
<div className="rowAlignLeft" style={{ margin: 0, padding: 0 }}>
<h3 style={{ margin: 0, padding: 0 }}>{obj.header}</h3>
</div>
<div className="row" style={{}}>
<div className="strokeSmollLeft" style={{ margin: 0, padding: 0 }}>
<div className="row" style={{ margin: 0 }}>
<div className="strokeSmollLeft" style={{ margin: 0, padding: 0 }}>
<h4 style={{ margin: 0, padding: 0 }}>{obj.title1}</h4>
<div className="rowAlignLeft" style={{ margin: 0, marginLeft: '1em', padding: 0 }}>
<p className="darkText">{obj.content1}</p>
<div className="rowAlignLeft" style={{ margin: 0, padding: 0 }}>
<p className="darkText">{obj.content1}</p>
</div>
</div>
<div className="strokeSmollLeft" style={{ margin: 0, padding: 0 }}>
<h4 style={{ margin: 0, padding: 0 }}>{obj.title2}</h4>
<div className="rowAlignLeft" style={{ margin: 0, marginLeft: '1em', padding: 0 }}>
<div className="rowAlignLeft" style={{ margin: 0, padding: 0 }}>
<p className="darkText">{obj.content2}</p>
</div>
</div>

View File

@ -140,10 +140,36 @@ svg {
from { top: 0; } to { top: calc(100vh - 69px); }
}
.metaSidebar {
#header {
height: 50px;
display: flex;
flex-direction: row;
align-items: center;
background-color: rgba(180, 175, 252, 0.80);
}
#bodyContent {
height: calc( 100vh - 50px);
max-height: calc( 100vh - 50px);
display: flex;
flex-wrap: nowrap;
overflow: hidden;
}
#sideContent {
background-color: rgba(180, 175, 252, 0.80);
backdrop-filter: blur(6px);
box-shadow: 9px 13px 8px 8px rgba(8, 7, 56, 0.692);
border-bottom-right-radius: 1em;
width: 400px;
}
.mainContent {
overflow: hidden;
justify-content: center;
align-content: center;
align-items: center;
flex-basis: 0;
flex-grow: 999;
}
.fullGrafana {
width: 100%;
@ -194,6 +220,10 @@ svg {
width: auto;
}
.forceWrap{
flex-direction: column;
}
.stroke {
box-sizing: border-box;
padding-bottom: 20px;