mirror of
https://github.com/stronk-dev/LivepeerEvents.git
synced 2025-07-05 10:45:10 +02:00
View ticket redemptions (WIP)
Added Ticket Broker contract to watch for ticket redemptions Make links open in a new tab Slight formatting updates
This commit is contained in:
parent
9fa66f5683
commit
ad5fe145cc
File diff suppressed because one or more lines are too long
1116
backend/src/abi/TicketBrokerTarget.json
Normal file
1116
backend/src/abi/TicketBrokerTarget.json
Normal file
File diff suppressed because one or more lines are too long
35
backend/src/models/ticketEvent.js
Normal file
35
backend/src/models/ticketEvent.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import mongoose from 'mongoose';
|
||||||
|
|
||||||
|
const TicketSchema = new mongoose.Schema({
|
||||||
|
address: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
transactionHash: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
transactionUrl: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
blockNumber: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
blockTime: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}, { timestamps: false });
|
||||||
|
|
||||||
|
const Ticket = mongoose.model('Ticket', TicketSchema);
|
||||||
|
export default Ticket;
|
@ -1,6 +1,8 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
import Event from '../models/event';
|
import Event from '../models/event';
|
||||||
import Block from '../models/block';
|
import Block from '../models/block';
|
||||||
|
import Ticket from '../models/ticketEvent'
|
||||||
|
|
||||||
const apiRouter = express.Router();
|
const apiRouter = express.Router();
|
||||||
import {
|
import {
|
||||||
API_CMC, API_L1_HTTP, API_L2_HTTP, API_L2_WS,
|
API_CMC, API_L1_HTTP, API_L2_HTTP, API_L2_WS,
|
||||||
@ -76,19 +78,32 @@ let delegatorCache = [];
|
|||||||
|
|
||||||
// Listen to smart contract emitters. Only re-syncs on boot!
|
// Listen to smart contract emitters. Only re-syncs on boot!
|
||||||
let eventsCache = [];
|
let eventsCache = [];
|
||||||
let latestMissedDuringSync = 0;
|
let latestBlockInChain = 0;
|
||||||
let lastBlockDataAdded = 0;
|
let lastBlockEvents = 0;
|
||||||
|
let lastBlockTickets = 0;
|
||||||
let syncCache = [];
|
let syncCache = [];
|
||||||
|
let ticketsCache = [];
|
||||||
|
let ticketsSyncCache = [];
|
||||||
// https://arbiscan.io/address/0x35Bcf3c30594191d53231E4FF333E8A770453e40#events
|
// https://arbiscan.io/address/0x35Bcf3c30594191d53231E4FF333E8A770453e40#events
|
||||||
let BondingManagerTargetJson;
|
let BondingManagerTargetJson;
|
||||||
let BondingManagerTargetAbi;
|
let BondingManagerTargetAbi;
|
||||||
let BondingManagerProxyAddr;
|
let BondingManagerProxyAddr;
|
||||||
let contractInstance;
|
let bondingManagerContract;
|
||||||
|
let TicketBrokerTargetJson;
|
||||||
|
let TicketBrokerTargetAbi;
|
||||||
|
let TicketBrokerTargetAddr;
|
||||||
|
let ticketBrokerContract;
|
||||||
if (!CONF_SIMPLE_MODE) {
|
if (!CONF_SIMPLE_MODE) {
|
||||||
|
// Listen for events on the bonding manager contract
|
||||||
BondingManagerTargetJson = fs.readFileSync('src/abi/BondingManagerTarget.json');
|
BondingManagerTargetJson = fs.readFileSync('src/abi/BondingManagerTarget.json');
|
||||||
BondingManagerTargetAbi = JSON.parse(BondingManagerTargetJson);
|
BondingManagerTargetAbi = JSON.parse(BondingManagerTargetJson);
|
||||||
BondingManagerProxyAddr = "0x35Bcf3c30594191d53231E4FF333E8A770453e40";
|
BondingManagerProxyAddr = "0x35Bcf3c30594191d53231E4FF333E8A770453e40";
|
||||||
contractInstance = new web3layer2WS.eth.Contract(BondingManagerTargetAbi.abi, BondingManagerProxyAddr);
|
bondingManagerContract = new web3layer2WS.eth.Contract(BondingManagerTargetAbi.abi, BondingManagerProxyAddr);
|
||||||
|
// Listen for events on the ticket broker contract
|
||||||
|
TicketBrokerTargetJson = fs.readFileSync('src/abi/TicketBrokerTarget.json');
|
||||||
|
TicketBrokerTargetAbi = JSON.parse(TicketBrokerTargetJson);
|
||||||
|
TicketBrokerTargetAddr = "0xa8bB618B1520E284046F3dFc448851A1Ff26e41B";
|
||||||
|
ticketBrokerContract = new web3layer2WS.eth.Contract(TicketBrokerTargetAbi.abi, TicketBrokerTargetAddr);
|
||||||
}
|
}
|
||||||
|
|
||||||
let blockCache = [];
|
let blockCache = [];
|
||||||
@ -117,11 +132,13 @@ const getBlock = async function (blockNumber) {
|
|||||||
|
|
||||||
// Set special flag to make sure also get blocks that pass us by while we are syncing
|
// Set special flag to make sure also get blocks that pass us by while we are syncing
|
||||||
let isSyncing = true;
|
let isSyncing = true;
|
||||||
let isSyncRunning = false;
|
let isEventSyncing = false;
|
||||||
|
let isTicketSyncing = false;
|
||||||
// Start Listening for live updates
|
// Start Listening for live updates
|
||||||
var BondingManagerProxyListener;
|
var BondingManagerProxyListener;
|
||||||
|
var TicketBrokerProxyListener;
|
||||||
if (!CONF_SIMPLE_MODE) {
|
if (!CONF_SIMPLE_MODE) {
|
||||||
BondingManagerProxyListener = contractInstance.events.allEvents(async (error, event) => {
|
BondingManagerProxyListener = bondingManagerContract.events.allEvents(async (error, event) => {
|
||||||
try {
|
try {
|
||||||
if (error) {
|
if (error) {
|
||||||
throw error
|
throw error
|
||||||
@ -157,14 +174,50 @@ if (!CONF_SIMPLE_MODE) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
console.log("Listening for events on " + BondingManagerProxyAddr);
|
console.log("Listening for events on " + BondingManagerProxyAddr);
|
||||||
|
TicketBrokerProxyListener = ticketBrokerContract.events.allEvents(async (error, event) => {
|
||||||
|
try {
|
||||||
|
if (error) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
if (isSyncing) {
|
||||||
|
console.log('Received new ticket event on block ' + event.blockNumber + " during sync");
|
||||||
|
} else {
|
||||||
|
console.log('Received new ticket event on block ' + event.blockNumber);
|
||||||
|
}
|
||||||
|
const thisBlock = await getBlock(event.blockNumber);
|
||||||
|
// Push obj of event to cache and create a new entry for it in the DB
|
||||||
|
const eventObj = {
|
||||||
|
address: event.address,
|
||||||
|
transactionHash: event.transactionHash,
|
||||||
|
transactionUrl: "https://arbiscan.io/tx/" + event.transactionHash,
|
||||||
|
name: event.event,
|
||||||
|
data: event.returnValues,
|
||||||
|
blockNumber: thisBlock.number,
|
||||||
|
blockTime: thisBlock.timestamp
|
||||||
|
}
|
||||||
|
if (!isSyncing) {
|
||||||
|
if (!CONF_DISABLE_DB) {
|
||||||
|
const dbObj = new Ticket(eventObj);
|
||||||
|
await dbObj.save();
|
||||||
|
}
|
||||||
|
ticketsCache.push(eventObj);
|
||||||
|
} else {
|
||||||
|
ticketsSyncCache.push(eventObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log("FATAL ERROR: ", err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log("Listening for tickets on " + TicketBrokerTargetAddr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Does the syncing
|
// Syncs events database
|
||||||
const doSync = function () {
|
const syncEvents = function () {
|
||||||
console.log("Starting sync process");
|
console.log("Starting sync process for Bonding Manager events");
|
||||||
isSyncRunning = true;
|
isEventSyncing = true;
|
||||||
// Then do a sync from last found until latest known
|
// Then do a sync from last found until latest known
|
||||||
contractInstance.getPastEvents("allEvents", { fromBlock: lastBlockDataAdded + 1, toBlock: 'latest' }, async (error, events) => {
|
bondingManagerContract.getPastEvents("allEvents", { fromBlock: lastBlockEvents + 1, toBlock: 'latest' }, async (error, events) => {
|
||||||
try {
|
try {
|
||||||
if (error) {
|
if (error) {
|
||||||
throw error
|
throw error
|
||||||
@ -172,8 +225,8 @@ const doSync = function () {
|
|||||||
let size = events.length;
|
let size = events.length;
|
||||||
console.log("Parsing " + size + " events");
|
console.log("Parsing " + size + " events");
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
if (event.blockNumber > lastBlockDataAdded) {
|
if (event.blockNumber > lastBlockEvents) {
|
||||||
lastBlockDataAdded = event.blockNumber;
|
lastBlockEvents = event.blockNumber;
|
||||||
}
|
}
|
||||||
const thisBlock = await getBlock(event.blockNumber);
|
const thisBlock = await getBlock(event.blockNumber);
|
||||||
const eventObj = {
|
const eventObj = {
|
||||||
@ -195,7 +248,46 @@ const doSync = function () {
|
|||||||
catch (err) {
|
catch (err) {
|
||||||
console.log("FATAL ERROR: ", err);
|
console.log("FATAL ERROR: ", err);
|
||||||
}
|
}
|
||||||
isSyncRunning = false;
|
isEventSyncing = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Syncs tickets database
|
||||||
|
const syncTickets = function () {
|
||||||
|
console.log("Starting sync process for Ticket Broker events");
|
||||||
|
isTicketSyncing = true;
|
||||||
|
// Then do a sync from last found until latest known
|
||||||
|
ticketBrokerContract.getPastEvents("allEvents", { fromBlock: lastBlockTickets + 1, toBlock: 'latest' }, async (error, events) => {
|
||||||
|
try {
|
||||||
|
if (error) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
let size = events.length;
|
||||||
|
console.log("Parsing " + size + " tickets");
|
||||||
|
for (const event of events) {
|
||||||
|
if (event.blockNumber > lastBlockTickets) {
|
||||||
|
lastBlockTickets = event.blockNumber;
|
||||||
|
}
|
||||||
|
const thisBlock = await getBlock(event.blockNumber);
|
||||||
|
const eventObj = {
|
||||||
|
address: event.address,
|
||||||
|
transactionHash: event.transactionHash,
|
||||||
|
transactionUrl: "https://arbiscan.io/tx/" + event.transactionHash,
|
||||||
|
name: event.event,
|
||||||
|
data: event.returnValues,
|
||||||
|
blockNumber: thisBlock.number,
|
||||||
|
blockTime: thisBlock.timestamp
|
||||||
|
}
|
||||||
|
if (!CONF_DISABLE_DB) {
|
||||||
|
const dbObj = new Ticket(eventObj);
|
||||||
|
await dbObj.save();
|
||||||
|
}
|
||||||
|
ticketsCache.push(eventObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log("FATAL ERROR: ", err);
|
||||||
|
}
|
||||||
|
isTicketSyncing = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function sleep(ms) {
|
function sleep(ms) {
|
||||||
@ -206,6 +298,13 @@ function sleep(ms) {
|
|||||||
|
|
||||||
const handleSync = async function () {
|
const handleSync = async function () {
|
||||||
// First collection -> cache
|
// First collection -> cache
|
||||||
|
// Get all parsed blocks
|
||||||
|
blockCache = await Block.find({}, {
|
||||||
|
blockNumber: 1,
|
||||||
|
blockTime: 1
|
||||||
|
});
|
||||||
|
console.log("Retrieved existing Blocks of size " + blockCache.length);
|
||||||
|
// Get all parsed Events
|
||||||
eventsCache = await Event.find({}, {
|
eventsCache = await Event.find({}, {
|
||||||
address: 1,
|
address: 1,
|
||||||
transactionHash: 1,
|
transactionHash: 1,
|
||||||
@ -217,31 +316,54 @@ const handleSync = async function () {
|
|||||||
_id: 0
|
_id: 0
|
||||||
});
|
});
|
||||||
console.log("Retrieved existing Events of size " + eventsCache.length);
|
console.log("Retrieved existing Events of size " + eventsCache.length);
|
||||||
|
// Get all parsedTickets
|
||||||
|
ticketsCache = await Ticket.find({}, {
|
||||||
|
address: 1,
|
||||||
|
transactionHash: 1,
|
||||||
|
transactionUrl: 1,
|
||||||
|
name: 1,
|
||||||
|
data: 1,
|
||||||
|
blockNumber: 1,
|
||||||
|
blockTime: 1,
|
||||||
|
_id: 0
|
||||||
|
});
|
||||||
|
console.log("Retrieved existing Tickets of size " + ticketsCache.length);
|
||||||
// Then determine latest block number parsed based on collection
|
// Then determine latest block number parsed based on collection
|
||||||
for (var idx = 0; idx < eventsCache.length; idx++) {
|
for (var idx = 0; idx < eventsCache.length; idx++) {
|
||||||
const thisBlock = eventsCache[idx];
|
const thisBlock = eventsCache[idx];
|
||||||
if (thisBlock.blockNumber > lastBlockDataAdded) {
|
if (thisBlock.blockNumber > lastBlockEvents) {
|
||||||
lastBlockDataAdded = thisBlock.blockNumber;
|
lastBlockEvents = thisBlock.blockNumber;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
console.log("Latest Event block parsed is " + lastBlockEvents);
|
||||||
|
// Then determine latest block number parsed based on collection
|
||||||
|
for (var idx = 0; idx < ticketsCache.length; idx++) {
|
||||||
|
const thisBlock = ticketsCache[idx];
|
||||||
|
if (thisBlock.blockNumber > lastBlockTickets) {
|
||||||
|
lastBlockTickets = thisBlock.blockNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log("Latest Ticket block parsed is " + lastBlockTickets);
|
||||||
// Get latest block in chain
|
// Get latest block in chain
|
||||||
const latestBlock = await web3layer2.eth.getBlockNumber();
|
const latestBlock = await web3layer2.eth.getBlockNumber();
|
||||||
if (latestBlock > latestMissedDuringSync) {
|
if (latestBlock > latestBlockInChain) {
|
||||||
latestMissedDuringSync = latestBlock;
|
latestBlockInChain = latestBlock;
|
||||||
}
|
}
|
||||||
console.log("Parsed up to block " + lastBlockDataAdded + " out of " + latestMissedDuringSync + " blocks");
|
console.log("Latest L2 Eth block is " + latestBlockInChain);
|
||||||
// Get all parsed blocks
|
console.log("Needs to sync " + (latestBlockInChain - lastBlockEvents) + " blocks for Events sync");
|
||||||
blockCache = await Block.find({}, {
|
console.log("Needs to sync " + (latestBlockInChain - lastBlockTickets) + " blocks for Tickets sync");
|
||||||
blockNumber: 1,
|
syncTickets();
|
||||||
blockTime: 1
|
syncEvents();
|
||||||
});
|
while (isEventSyncing || isTicketSyncing) {
|
||||||
console.log("Retrieved existing Blocks of size " + blockCache.length);
|
await sleep(3000);
|
||||||
doSync();
|
if (isEventSyncing){
|
||||||
while (isSyncRunning) {
|
console.log("Parsed " + lastBlockEvents + " out of " + latestBlockInChain + " blocks for Event sync");
|
||||||
await sleep(1000);
|
|
||||||
console.log("Parsed " + lastBlockDataAdded + " out of " + latestMissedDuringSync + " blocks");
|
|
||||||
}
|
}
|
||||||
while (syncCache.length) {
|
if (isTicketSyncing){
|
||||||
|
console.log("Parsed " + lastBlockTickets + " out of " + latestBlockInChain + " blocks for Ticket sync");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (syncCache.length || ticketsSyncCache.length) {
|
||||||
const liveEvents = syncCache;
|
const liveEvents = syncCache;
|
||||||
syncCache = [];
|
syncCache = [];
|
||||||
for (const eventObj of liveEvents) {
|
for (const eventObj of liveEvents) {
|
||||||
@ -252,16 +374,27 @@ const handleSync = async function () {
|
|||||||
}
|
}
|
||||||
eventsCache.push(eventObj);
|
eventsCache.push(eventObj);
|
||||||
}
|
}
|
||||||
|
const liveTickets = ticketsSyncCache;
|
||||||
|
ticketsSyncCache = [];
|
||||||
|
for (const eventObj of liveTickets) {
|
||||||
|
console.log("Parsing ticket received while syncing");
|
||||||
|
if (!CONF_DISABLE_DB) {
|
||||||
|
const dbObj = new Ticket(eventObj);
|
||||||
|
await dbObj.save();
|
||||||
|
}
|
||||||
|
ticketsCache.push(eventObj);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
console.log('done syncing')
|
console.log('done syncing')
|
||||||
isSyncing = false;
|
isSyncing = false;
|
||||||
};
|
};
|
||||||
if (!isSyncRunning && !CONF_SIMPLE_MODE && !CONF_DISABLE_SYNC) {
|
if (!isEventSyncing && !CONF_SIMPLE_MODE && !CONF_DISABLE_SYNC) {
|
||||||
handleSync();
|
handleSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Splits of raw CMC object into coin quote data
|
// Splits of raw CMC object into coin quote data
|
||||||
const parseCmc = async function () {
|
const parseCmc = async function () {
|
||||||
|
return;
|
||||||
try {
|
try {
|
||||||
cmcCache = await cmcClient.getTickers({ limit: 200 });
|
cmcCache = await cmcClient.getTickers({ limit: 200 });
|
||||||
for (var idx = 0; idx < cmcCache.data.length; idx++) {
|
for (var idx = 0; idx < cmcCache.data.length; idx++) {
|
||||||
@ -422,6 +555,15 @@ apiRouter.get("/getEvents", async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Exports list of smart contract ticket events
|
||||||
|
apiRouter.get("/getTickets", async (req, res) => {
|
||||||
|
try {
|
||||||
|
res.send(ticketsCache);
|
||||||
|
} catch (err) {
|
||||||
|
res.status(400).send(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Gets info on a given Orchestrator
|
// Gets info on a given Orchestrator
|
||||||
const parseOrchestrator = async function (reqAddr) {
|
const parseOrchestrator = async function (reqAddr) {
|
||||||
reqAddr = reqAddr.toLowerCase();
|
reqAddr = reqAddr.toLowerCase();
|
||||||
|
@ -7,11 +7,11 @@ const Block = (obj) => {
|
|||||||
const [thisDate, thisTime] = dateObj.toISOString().split('T');
|
const [thisDate, thisTime] = dateObj.toISOString().split('T');
|
||||||
return (
|
return (
|
||||||
<div className="rowAlignLeft" style={{ width: '100%', marginTop: '1em' }}>
|
<div className="rowAlignLeft" style={{ width: '100%', marginTop: '1em' }}>
|
||||||
<a className="selectOrch" style={{cursor: 'alias'}} href={obj.url}>
|
<a className="selectOrch" style={{cursor: 'alias'}} target="_blank" href={obj.url}>
|
||||||
<img alt="" src="arb.svg" width="30em" height="30em" />
|
<img alt="" src="arb.svg" width="30em" height="30em" />
|
||||||
</a>
|
</a>
|
||||||
<span className="rowAlignRight elipsText">
|
<span className="rowAlignRight elipsText">
|
||||||
<a className="selectOrch" style={{cursor: 'alias'}} href={"https://arbiscan.io/block/" + obj.block}>
|
<a className="selectOrch" style={{cursor: 'alias'}} target="_blank" href={"https://arbiscan.io/block/" + obj.block}>
|
||||||
🔗{obj.block}
|
🔗{obj.block}
|
||||||
</a>
|
</a>
|
||||||
<p className="darkText">📅{thisDate} - {thisTime.split('.')[0]} </p>
|
<p className="darkText">📅{thisDate} - {thisTime.split('.')[0]} </p>
|
||||||
|
@ -4,7 +4,7 @@ import ReactTooltip from "react-tooltip";
|
|||||||
const Address = (obj) => {
|
const Address = (obj) => {
|
||||||
return (
|
return (
|
||||||
<div className="rowAlignLeft" style={{ width: 'unset', margin: 0 }}>
|
<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} >
|
<a className="selectOrchLight" target="_blank" href={"https://explorer.livepeer.org/accounts/" + obj.address} data-tip data-for={obj.seed} >
|
||||||
<div className="rowAlignLeft" style={{ width: 'unset', margin: 0 }}>
|
<div className="rowAlignLeft" style={{ width: 'unset', margin: 0 }}>
|
||||||
<img alt="" src="livepeer.png" width="20" height="20" />
|
<img alt="" src="livepeer.png" width="20" height="20" />
|
||||||
<span className="elipsText elipsOnMobile">{obj.address}</span>
|
<span className="elipsText elipsOnMobile">{obj.address}</span>
|
||||||
|
@ -17,7 +17,7 @@ const OrchDelegatorViewer = (obj) => {
|
|||||||
{
|
{
|
||||||
delegators.map((delObj, idx) => {
|
delegators.map((delObj, idx) => {
|
||||||
return (
|
return (
|
||||||
<div className={obj.forceVertical ? "flexContainer forceWrap" : "flexContainer"} key={"delegator" + idx} style={{ margin: 0, textAlign: 'center',alignItems: 'center', justifyContent:'center' }}>
|
<div className="flexContainer forceWrap" key={"delegator" + idx} style={{ margin: 0, textAlign: 'center',alignItems: 'center', justifyContent:'center' }}>
|
||||||
<Address address={delObj.id} seed={"delegator" + idx + delObj.id} />
|
<Address address={delObj.id} seed={"delegator" + idx + delObj.id} />
|
||||||
<div className="rowAlignRight" style={{ margin: 0 }}>
|
<div className="rowAlignRight" style={{ margin: 0 }}>
|
||||||
<p className="darkText">{parseFloat(delObj.bondedAmount).toFixed(2)} LPT since round {delObj.startRound}</p>
|
<p className="darkText">{parseFloat(delObj.bondedAmount).toFixed(2)} LPT since round {delObj.startRound}</p>
|
||||||
|
@ -9,6 +9,9 @@ const stakeColour = "rgba(56, 23, 122, 0.3)";
|
|||||||
const unbondColour = "rgba(122, 23, 51, 0.3)";
|
const unbondColour = "rgba(122, 23, 51, 0.3)";
|
||||||
const claimColour = "rgba(77, 91, 42, 0.3)";
|
const claimColour = "rgba(77, 91, 42, 0.3)";
|
||||||
|
|
||||||
|
const ticketTransferColour = "rgba(88, 91, 42, 0.3)";
|
||||||
|
const ticketRedeemColour = "rgba(42, 91, 44, 0.3)";
|
||||||
|
|
||||||
const thresholdStaking = 0.001;
|
const thresholdStaking = 0.001;
|
||||||
const thresholdFees = 0.00009;
|
const thresholdFees = 0.00009;
|
||||||
|
|
||||||
@ -18,6 +21,7 @@ export const RECEIVE_EVENTS = "RECEIVE_EVENTS";
|
|||||||
export const RECEIVE_CURRENT_ORCHESTRATOR = "RECEIVE_CURRENT_ORCHESTRATOR";
|
export const RECEIVE_CURRENT_ORCHESTRATOR = "RECEIVE_CURRENT_ORCHESTRATOR";
|
||||||
export const RECEIVE_ORCHESTRATOR = "RECEIVE_ORCHESTRATOR";
|
export const RECEIVE_ORCHESTRATOR = "RECEIVE_ORCHESTRATOR";
|
||||||
export const CLEAR_ORCHESTRATOR = "CLEAR_ORCHESTRATOR";
|
export const CLEAR_ORCHESTRATOR = "CLEAR_ORCHESTRATOR";
|
||||||
|
export const RECEIVE_TICKETS = "RECEIVE_TICKETS";
|
||||||
|
|
||||||
const setQuotes = message => ({
|
const setQuotes = message => ({
|
||||||
type: RECEIVE_QUOTES, message
|
type: RECEIVE_QUOTES, message
|
||||||
@ -37,6 +41,9 @@ const setOrchestratorInfo = message => ({
|
|||||||
const clearOrchestratorInfo = () => ({
|
const clearOrchestratorInfo = () => ({
|
||||||
type: CLEAR_ORCHESTRATOR
|
type: CLEAR_ORCHESTRATOR
|
||||||
})
|
})
|
||||||
|
const setTickets = message => ({
|
||||||
|
type: RECEIVE_TICKETS, message
|
||||||
|
});
|
||||||
|
|
||||||
export const getQuotes = () => async dispatch => {
|
export const getQuotes = () => async dispatch => {
|
||||||
const response = await apiUtil.getQuotes();
|
const response = await apiUtil.getQuotes();
|
||||||
@ -349,6 +356,83 @@ export const getEvents = () => async dispatch => {
|
|||||||
return dispatch(receiveErrors(data));
|
return dispatch(receiveErrors(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getTickets = () => async dispatch => {
|
||||||
|
const response = await apiUtil.getTickets();
|
||||||
|
const data = await response.json();
|
||||||
|
// Combine raw list of events into a list of useful Events
|
||||||
|
if (response.ok) {
|
||||||
|
let finalTicketList = [];
|
||||||
|
// Current transaction we are processing
|
||||||
|
let txCounter = 0;
|
||||||
|
let currentTx = "";
|
||||||
|
let currentUrl = "";
|
||||||
|
let currentBlock = 0;
|
||||||
|
let currentTime = 0;
|
||||||
|
// Parse Tickets
|
||||||
|
{
|
||||||
|
for (const eventObj of data.slice(0).reverse()) {
|
||||||
|
if (currentTx === "") {
|
||||||
|
currentTx = eventObj.transactionHash;
|
||||||
|
currentUrl = eventObj.transactionUrl;
|
||||||
|
currentBlock = eventObj.blockNumber;
|
||||||
|
currentTime = eventObj.blockTime;
|
||||||
|
}
|
||||||
|
// New transaction found
|
||||||
|
if (currentTx !== eventObj.transactionHash) {
|
||||||
|
// Reset event data
|
||||||
|
txCounter++;
|
||||||
|
currentTx = eventObj.transactionHash;
|
||||||
|
currentUrl = eventObj.transactionUrl;
|
||||||
|
currentBlock = eventObj.blockNumber;
|
||||||
|
currentTime = eventObj.blockTime;
|
||||||
|
}
|
||||||
|
// Always split off WithdrawStake as a separate Withdraw Event
|
||||||
|
if (eventObj.name === "WinningTicketRedeemed") {
|
||||||
|
const amount = parseFloat(eventObj.data.faceValue) / 1000000000000000000;
|
||||||
|
const txt = " redeemed a winning ticket worth " + amount.toFixed(4) + " Eth";
|
||||||
|
finalTicketList.push({
|
||||||
|
eventType: "Withdraw",
|
||||||
|
eventDescription: txt,
|
||||||
|
eventCaller: eventObj.data.recipient.toLowerCase(),
|
||||||
|
eventFrom: eventObj.data.sender.toLowerCase(),
|
||||||
|
eventTo: "",
|
||||||
|
eventColour: ticketRedeemColour,
|
||||||
|
transactionHash: currentTx,
|
||||||
|
transactionUrl: currentUrl,
|
||||||
|
transactionBlock: currentBlock,
|
||||||
|
transactionTime: currentTime,
|
||||||
|
eventValue: amount
|
||||||
|
});
|
||||||
|
} else if (eventObj.name === "WinningTicketTransfer") {
|
||||||
|
// For now lets just ignore these, they are boring
|
||||||
|
continue;
|
||||||
|
const amount = parseFloat(eventObj.data.amount) / 1000000000000000000;
|
||||||
|
const txt = " broadcaster payed out " + amount.toFixed(4) + " Eth";
|
||||||
|
finalTicketList.push({
|
||||||
|
eventType: "TransferTicket",
|
||||||
|
eventDescription: txt,
|
||||||
|
eventCaller: eventObj.data.sender.toLowerCase(),
|
||||||
|
eventFrom: "",
|
||||||
|
eventTo: eventObj.data.recipient.toLowerCase(),
|
||||||
|
eventColour: ticketTransferColour,
|
||||||
|
transactionHash: currentTx,
|
||||||
|
transactionUrl: currentUrl,
|
||||||
|
transactionBlock: currentBlock,
|
||||||
|
transactionTime: currentTime,
|
||||||
|
eventValue: amount
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log("UNIMPLEMENTED: " + eventObj.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// NOTE: We are throwing away the very oldest Ticket now, which should be fine.
|
||||||
|
// We can fix this once above wall of text becomes a separate function
|
||||||
|
return dispatch(setTickets(finalTicketList));
|
||||||
|
}
|
||||||
|
return dispatch(receiveErrors(data));
|
||||||
|
};
|
||||||
|
|
||||||
export const getCurrentOrchestratorInfo = () => async dispatch => {
|
export const getCurrentOrchestratorInfo = () => async dispatch => {
|
||||||
const response = await apiUtil.getCurrentOrchestratorInfo();
|
const response = await apiUtil.getCurrentOrchestratorInfo();
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
@ -362,9 +446,9 @@ export const getOrchestratorInfo = (orchAddr) => async dispatch => {
|
|||||||
const response = await apiUtil.getOrchestratorInfo(orchAddr);
|
const response = await apiUtil.getOrchestratorInfo(orchAddr);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
if (data && data.id){
|
if (data && data.id) {
|
||||||
return dispatch(setOrchestratorInfo(data));
|
return dispatch(setOrchestratorInfo(data));
|
||||||
}else{
|
} else {
|
||||||
const response = await apiUtil.getOrchestratorByDelegator(orchAddr);
|
const response = await apiUtil.getOrchestratorByDelegator(orchAddr);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
|
@ -25,8 +25,8 @@ const EventButton = (obj) => {
|
|||||||
if (obj.eventObj.eventTo) {
|
if (obj.eventObj.eventTo) {
|
||||||
eventTo =
|
eventTo =
|
||||||
<div className="rowAlignLeft" style={{ width: '100%', margin: 0, marginLeft: '0.5em' }}>
|
<div className="rowAlignLeft" style={{ width: '100%', margin: 0, marginLeft: '0.5em' }}>
|
||||||
<p>To</p>
|
<span>To :</span>
|
||||||
<a className="selectOrch" style={{ cursor: 'alias' }} href={"https://explorer.livepeer.org/accounts/" + obj.eventObj.eventTo}>
|
<a className="selectOrch" style={{ cursor: 'alias', marginLeft: '0.5em' }} target="_blank" href={"https://explorer.livepeer.org/accounts/" + obj.eventObj.eventTo}>
|
||||||
<img alt="" src="livepeer.png" width="20em" height="20em" style={{ margin: 0 }} />
|
<img alt="" src="livepeer.png" width="20em" height="20em" style={{ margin: 0 }} />
|
||||||
</a>
|
</a>
|
||||||
<button className="selectOrch" style={{ margin: 0, padding: '0.5em', cursor: 'pointer' }} onClick={() => { obj.setSearchTerm(obj.eventObj.eventTo) }} >
|
<button className="selectOrch" style={{ margin: 0, padding: '0.5em', cursor: 'pointer' }} onClick={() => { obj.setSearchTerm(obj.eventObj.eventTo) }} >
|
||||||
@ -40,8 +40,8 @@ const EventButton = (obj) => {
|
|||||||
if (obj.eventObj.eventFrom) {
|
if (obj.eventObj.eventFrom) {
|
||||||
eventFrom =
|
eventFrom =
|
||||||
<div className="rowAlignLeft" style={{ width: '100%', margin: 0, marginLeft: '0.5em' }}>
|
<div className="rowAlignLeft" style={{ width: '100%', margin: 0, marginLeft: '0.5em' }}>
|
||||||
<p>From</p>
|
<span>From :</span>
|
||||||
<a className="selectOrch" style={{ cursor: 'alias' }} href={"https://explorer.livepeer.org/accounts/" + obj.eventObj.eventFrom}>
|
<a className="selectOrch" style={{ cursor: 'alias', marginLeft: '0.5em' }} target="_blank" href={"https://explorer.livepeer.org/accounts/" + obj.eventObj.eventFrom}>
|
||||||
<img alt="" src="livepeer.png" width="20em" height="20em" style={{ margin: 0 }} />
|
<img alt="" src="livepeer.png" width="20em" height="20em" style={{ margin: 0 }} />
|
||||||
</a>
|
</a>
|
||||||
<button className="selectOrch" style={{ margin: 0, padding: '0.5em', cursor: 'pointer' }} onClick={() => { obj.setSearchTerm(obj.eventObj.eventFrom) }} >
|
<button className="selectOrch" style={{ margin: 0, padding: '0.5em', cursor: 'pointer' }} onClick={() => { obj.setSearchTerm(obj.eventObj.eventFrom) }} >
|
||||||
@ -55,15 +55,15 @@ const EventButton = (obj) => {
|
|||||||
if (obj.eventObj.eventCaller) {
|
if (obj.eventObj.eventCaller) {
|
||||||
eventCaller =
|
eventCaller =
|
||||||
<div className="rowAlignLeft" style={{ width: '100%', margin: 0, marginLeft: '0.5em' }}>
|
<div className="rowAlignLeft" style={{ width: '100%', margin: 0, marginLeft: '0.5em' }}>
|
||||||
<p>Caller</p>
|
<span>Caller :</span>
|
||||||
<a className="selectOrch" style={{ cursor: 'alias' }} href={"https://explorer.livepeer.org/accounts/" + obj.eventObj.eventCaller}>
|
<a className="selectOrch" style={{ cursor: 'alias', marginLeft: '0.5em' }} target="_blank" href={"https://explorer.livepeer.org/accounts/" + obj.eventObj.eventCaller}>
|
||||||
<img alt="" src="livepeer.png" width="20em" height="20em" style={{ margin: 0 }} />
|
<img alt="" src="livepeer.png" width="20em" height="20em" style={{ margin: 0 }} />
|
||||||
</a>
|
</a>
|
||||||
<button className="selectOrch" style={{ margin: 0, padding: '0.5em', cursor: 'pointer' }} onClick={() => { obj.setSearchTerm(obj.eventObj.eventCaller) }} >
|
<button className="selectOrch" style={{ margin: 0, padding: '0.5em', cursor: 'pointer' }} onClick={() => { obj.setSearchTerm(obj.eventObj.eventCaller) }} >
|
||||||
<span className="elipsText">🔎</span>
|
<span className="elipsText">🔎</span>
|
||||||
</button>
|
</button>
|
||||||
<button className="selectOrch" style={{ margin: 0, padding: 0, cursor: 'help' }} onClick={() => { dispatch(getOrchestratorInfo(obj.eventObj.eventCaller)) }} >
|
<button className="selectOrch" style={{ margin: 0, padding: 0, cursor: 'help' }} onClick={() => { dispatch(getOrchestratorInfo(obj.eventObj.eventCaller)) }} >
|
||||||
<p className="elipsText elipsOnMobileExtra">{obj.eventObj.eventCaller}</p>
|
<span className="elipsText elipsOnMobileExtra">{obj.eventObj.eventCaller}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import { getOrchestratorInfo, clearOrchestrator } from "./actions/livepeer";
|
|||||||
import EventViewer from "./eventViewer";
|
import EventViewer from "./eventViewer";
|
||||||
import Orchestrator from "./orchestratorViewer";
|
import Orchestrator from "./orchestratorViewer";
|
||||||
import Stat from "./statViewer";
|
import Stat from "./statViewer";
|
||||||
|
import TicketViewer from './ticketViewer';
|
||||||
|
|
||||||
// Shows the EventViewer and other Livepeer related info
|
// Shows the EventViewer and other Livepeer related info
|
||||||
const defaultMaxShown = 100;
|
const defaultMaxShown = 100;
|
||||||
@ -15,6 +16,7 @@ const Livepeer = (obj) => {
|
|||||||
const [maxAmount, setMaxAmount] = useState(defaultMaxShown);
|
const [maxAmount, setMaxAmount] = useState(defaultMaxShown);
|
||||||
const [prefill, setPrefill] = useSearchParams();
|
const [prefill, setPrefill] = useSearchParams();
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
|
const [showTickets, setShowTickets] = useState("");
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const livepeer = useSelector((state) => state.livepeerstate);
|
const livepeer = useSelector((state) => state.livepeerstate);
|
||||||
const [redirectToHome, setRedirectToHome] = useState(false);
|
const [redirectToHome, setRedirectToHome] = useState(false);
|
||||||
@ -129,6 +131,18 @@ const Livepeer = (obj) => {
|
|||||||
eventsList = livepeer.events;
|
eventsList = livepeer.events;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let ticketList = [];
|
||||||
|
let ticketBit;
|
||||||
|
if (livepeer.tickets) {
|
||||||
|
ticketList = livepeer.tickets;
|
||||||
|
}
|
||||||
|
if (showTickets) {
|
||||||
|
ticketBit =
|
||||||
|
<div className="rightContent">
|
||||||
|
<TicketViewer tickets={ticketList} forceVertical={true} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
let thisOrchObj;
|
let thisOrchObj;
|
||||||
let headerString;
|
let headerString;
|
||||||
if (livepeer.selectedOrchestrator) {
|
if (livepeer.selectedOrchestrator) {
|
||||||
@ -164,6 +178,7 @@ const Livepeer = (obj) => {
|
|||||||
</div >
|
</div >
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ margin: 0, padding: 0, height: '100%', width: '100%', overflow: 'hidden' }}>
|
<div style={{ margin: 0, padding: 0, height: '100%', width: '100%', overflow: 'hidden' }}>
|
||||||
<div id='header'>
|
<div id='header'>
|
||||||
@ -184,16 +199,24 @@ const Livepeer = (obj) => {
|
|||||||
}}>
|
}}>
|
||||||
<h4>✖️ Clear</h4>
|
<h4>✖️ Clear</h4>
|
||||||
</button>
|
</button>
|
||||||
<button className="homeButton" style={{ padding: 0, paddingRight: '1em', paddingLeft: '1em' }} onClick={() => {
|
<p>Tickets</p>
|
||||||
setShowSidebar(!showSidebar);
|
<div className="toggle-container" onClick={() => setShowTickets(!showTickets)}>
|
||||||
}}>
|
<div className={`dialog-button ${showTickets ? "" : "disabled"}`}>
|
||||||
<h4>🔎 Sidebar</h4>
|
{showTickets ? "Show" : "Hide"}
|
||||||
</button>
|
</div>
|
||||||
<button className="homeButton" style={{ padding: 0, paddingRight: '1em', paddingLeft: '1em' }} onClick={() => {
|
</div>
|
||||||
setShowFilter(!showFilter);
|
<p>Sidebar</p>
|
||||||
}}>
|
<div className="toggle-container" onClick={() => setShowSidebar(!showSidebar)}>
|
||||||
<h4>🛠️ Filter</h4>
|
<div className={`dialog-button ${showSidebar ? "" : "disabled"}`}>
|
||||||
</button>
|
{showSidebar ? "Show" : "Hide"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p>Filter</p>
|
||||||
|
<div className="toggle-container" onClick={() => setShowFilter(!showFilter)}>
|
||||||
|
<div className={`dialog-button ${showFilter ? "" : "disabled"}`}>
|
||||||
|
{showFilter ? "Show" : "Hide"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id='bodyContent'>
|
<div id='bodyContent'>
|
||||||
@ -201,8 +224,9 @@ const Livepeer = (obj) => {
|
|||||||
<div className="mainContent">
|
<div className="mainContent">
|
||||||
<EventViewer events={eventsList} searchTerm={searchTerm} setSearchTerm={setSearchTerm}
|
<EventViewer events={eventsList} searchTerm={searchTerm} setSearchTerm={setSearchTerm}
|
||||||
forceVertical={true} showFilter={showFilter} setAmountFilter={setAmountFilter} amountFilter={amountFilter}
|
forceVertical={true} showFilter={showFilter} setAmountFilter={setAmountFilter} amountFilter={amountFilter}
|
||||||
maxAmount={maxAmount} setMaxAmount={setMaxAmount}/>
|
maxAmount={maxAmount} setMaxAmount={setMaxAmount} />
|
||||||
</div>
|
</div>
|
||||||
|
{ticketBit}
|
||||||
</div>
|
</div>
|
||||||
</div >
|
</div >
|
||||||
);
|
);
|
||||||
|
@ -4,7 +4,7 @@ import {
|
|||||||
getVisitorStats
|
getVisitorStats
|
||||||
} from "./actions/user";
|
} from "./actions/user";
|
||||||
import {
|
import {
|
||||||
getQuotes, getBlockchainData, getEvents, getCurrentOrchestratorInfo
|
getQuotes, getBlockchainData, getEvents, getCurrentOrchestratorInfo, getTickets
|
||||||
} from "./actions/livepeer";
|
} from "./actions/livepeer";
|
||||||
import { login } from "./actions/session";
|
import { login } from "./actions/session";
|
||||||
|
|
||||||
@ -24,6 +24,7 @@ const Startup = (obj) => {
|
|||||||
dispatch(getEvents());
|
dispatch(getEvents());
|
||||||
dispatch(getBlockchainData());
|
dispatch(getBlockchainData());
|
||||||
dispatch(getCurrentOrchestratorInfo());
|
dispatch(getCurrentOrchestratorInfo());
|
||||||
|
dispatch(getTickets());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ const Orchestrator = (obj) => {
|
|||||||
totalVolumeUSD={obj.thisOrchestrator.totalVolumeUSD}
|
totalVolumeUSD={obj.thisOrchestrator.totalVolumeUSD}
|
||||||
delegator={obj.thisOrchestrator.delegator}
|
delegator={obj.thisOrchestrator.delegator}
|
||||||
/>
|
/>
|
||||||
<OrchDelegatorViewer delegators={obj.thisOrchestrator.delegators} forceVertical={obj.forceVertical} />
|
<OrchDelegatorViewer delegators={obj.thisOrchestrator.delegators} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -57,7 +57,7 @@ const Orchestrator = (obj) => {
|
|||||||
totalVolumeUSD={obj.thisOrchestrator.totalVolumeUSD}
|
totalVolumeUSD={obj.thisOrchestrator.totalVolumeUSD}
|
||||||
delegator={obj.thisOrchestrator.delegator}
|
delegator={obj.thisOrchestrator.delegator}
|
||||||
/>
|
/>
|
||||||
<OrchDelegatorViewer delegators={obj.thisOrchestrator.delegators} forceVertical={obj.forceVertical} />
|
<OrchDelegatorViewer delegators={obj.thisOrchestrator.delegators} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -4,7 +4,8 @@ import {
|
|||||||
RECEIVE_EVENTS,
|
RECEIVE_EVENTS,
|
||||||
RECEIVE_ORCHESTRATOR,
|
RECEIVE_ORCHESTRATOR,
|
||||||
RECEIVE_CURRENT_ORCHESTRATOR,
|
RECEIVE_CURRENT_ORCHESTRATOR,
|
||||||
CLEAR_ORCHESTRATOR
|
CLEAR_ORCHESTRATOR,
|
||||||
|
RECEIVE_TICKETS
|
||||||
} from "../../actions/livepeer";
|
} from "../../actions/livepeer";
|
||||||
|
|
||||||
export default (state = {}, { type, message }) => {
|
export default (state = {}, { type, message }) => {
|
||||||
@ -22,6 +23,8 @@ export default (state = {}, { type, message }) => {
|
|||||||
return { ...state, selectedOrchestrator: message };
|
return { ...state, selectedOrchestrator: message };
|
||||||
case CLEAR_ORCHESTRATOR:
|
case CLEAR_ORCHESTRATOR:
|
||||||
return { ...state, selectedOrchestrator: null };
|
return { ...state, selectedOrchestrator: null };
|
||||||
|
case RECEIVE_TICKETS:
|
||||||
|
return { ...state, tickets: message };
|
||||||
default:
|
default:
|
||||||
return { ...state };
|
return { ...state };
|
||||||
}
|
}
|
||||||
|
@ -171,6 +171,12 @@ svg {
|
|||||||
flex-basis: 0;
|
flex-basis: 0;
|
||||||
flex-grow: 999;
|
flex-grow: 999;
|
||||||
}
|
}
|
||||||
|
.rightContent {
|
||||||
|
overflow: hidden;
|
||||||
|
justify-content: center;
|
||||||
|
align-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.fullGrafana {
|
.fullGrafana {
|
||||||
@ -508,6 +514,46 @@ svg {
|
|||||||
border-radius: 1em;
|
border-radius: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toggle-container {
|
||||||
|
width: 70px;
|
||||||
|
background-color: #c4c4c4;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 2px;
|
||||||
|
height: 32px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-button {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #002b49;
|
||||||
|
color: white;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 18px;
|
||||||
|
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
|
||||||
|
min-width: 46px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 38px;
|
||||||
|
min-width: unset;
|
||||||
|
border-radius: 3px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.25);
|
||||||
|
position: absolute;
|
||||||
|
left: 34px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled {
|
||||||
|
background-color: #384e5c;
|
||||||
|
left: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-aspect-ratio: 1/1) {
|
@media (max-aspect-ratio: 1/1) {
|
||||||
.fullGrafana {
|
.fullGrafana {
|
||||||
width: calc(100vw - 2em);
|
width: calc(100vw - 2em);
|
||||||
|
49
src/ticketViewer.js
Normal file
49
src/ticketViewer.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import EventButton from "./eventButton";
|
||||||
|
import ScrollContainer from 'react-indiana-drag-scroll';
|
||||||
|
|
||||||
|
const TicketViewer = (obj) => {
|
||||||
|
console.log("Rendering TicketViewer");
|
||||||
|
let unfiltered = 0;
|
||||||
|
let prevBlock = 0;
|
||||||
|
let ticketList = [];
|
||||||
|
for (const ticketObj of obj.tickets) {
|
||||||
|
unfiltered++;
|
||||||
|
if (prevBlock === ticketObj.transactionBlock) {
|
||||||
|
ticketList.push(<EventButton
|
||||||
|
key={ticketObj.transactionHash + unfiltered}
|
||||||
|
eventObj={ticketObj}
|
||||||
|
setSearchTerm={obj.setSearchTerm}
|
||||||
|
/>);
|
||||||
|
} else {
|
||||||
|
prevBlock = ticketObj.transactionBlock;
|
||||||
|
ticketList.push(<EventButton
|
||||||
|
key={ticketObj.transactionHash + unfiltered}
|
||||||
|
eventObj={ticketObj}
|
||||||
|
isFirstOfBlock={prevBlock}
|
||||||
|
time={ticketObj.transactionTime}
|
||||||
|
setSearchTerm={obj.setSearchTerm}
|
||||||
|
/>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="strokeSmollLeft" style={{ padding: 0, margin: 0, height: 'calc( 100vh - 50px)' }}>
|
||||||
|
<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' }}>
|
||||||
|
{ticketList}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ScrollContainer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TicketViewer;
|
@ -27,6 +27,15 @@ export const getEvents = () => (
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const getTickets = () => (
|
||||||
|
fetch("api/livepeer/getTickets", {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
export const getCurrentOrchestratorInfo = () => (
|
export const getCurrentOrchestratorInfo = () => (
|
||||||
fetch("api/livepeer/getOrchestrator", {
|
fetch("api/livepeer/getOrchestrator", {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user