forked from solana-labs/solana
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(explorer): add Solana Ping to cluster stats page (solana-labs#23239
- Loading branch information
Showing
4 changed files
with
398 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,239 @@ | ||
import React from "react"; | ||
import classNames from "classnames"; | ||
import { | ||
PingRollupInfo, | ||
PingStatus, | ||
useSolanaPingInfo, | ||
} from "providers/stats/SolanaPingProvider"; | ||
import { Bar } from "react-chartjs-2"; | ||
import { ChartOptions, ChartTooltipModel } from "chart.js"; | ||
import { Cluster, useCluster } from "providers/cluster"; | ||
|
||
export function SolanaPingCard() { | ||
const { cluster } = useCluster(); | ||
|
||
if (cluster === Cluster.Custom) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<div className="card"> | ||
<div className="card-header"> | ||
<h4 className="card-header-title">Solana Ping Stats</h4> | ||
</div> | ||
<PingBarBody /> | ||
</div> | ||
); | ||
} | ||
|
||
function PingBarBody() { | ||
const pingInfo = useSolanaPingInfo(); | ||
|
||
if (pingInfo.status !== PingStatus.Ready) { | ||
return ( | ||
<StatsNotReady | ||
error={pingInfo.status === PingStatus.Error} | ||
retry={pingInfo.retry} | ||
/> | ||
); | ||
} | ||
|
||
return <PingBarChart pingInfo={pingInfo} />; | ||
} | ||
|
||
type StatsNotReadyProps = { error: boolean; retry?: Function }; | ||
function StatsNotReady({ error, retry }: StatsNotReadyProps) { | ||
if (error) { | ||
return ( | ||
<div className="card-body text-center"> | ||
There was a problem loading solana ping stats.{" "} | ||
{retry && ( | ||
<button | ||
className="btn btn-white btn-sm" | ||
onClick={() => { | ||
retry(); | ||
}} | ||
> | ||
<span className="fe fe-refresh-cw me-2"></span> | ||
Try Again | ||
</button> | ||
)} | ||
</div> | ||
); | ||
} | ||
|
||
return ( | ||
<div className="card-body text-center"> | ||
<span className="spinner-grow spinner-grow-sm me-2"></span> | ||
Loading | ||
</div> | ||
); | ||
} | ||
|
||
type Series = "short" | "medium" | "long"; | ||
const SERIES: Series[] = ["short", "medium", "long"]; | ||
const SERIES_INFO = { | ||
short: { | ||
label: (index: number) => index, | ||
interval: "30m", | ||
}, | ||
medium: { | ||
label: (index: number) => index * 4, | ||
interval: "2h", | ||
}, | ||
long: { | ||
label: (index: number) => index * 12, | ||
interval: "6h", | ||
}, | ||
}; | ||
|
||
const CUSTOM_TOOLTIP = function (this: any, tooltipModel: ChartTooltipModel) { | ||
// Tooltip Element | ||
let tooltipEl = document.getElementById("chartjs-tooltip"); | ||
|
||
// Create element on first render | ||
if (!tooltipEl) { | ||
tooltipEl = document.createElement("div"); | ||
tooltipEl.id = "chartjs-tooltip"; | ||
tooltipEl.innerHTML = `<div class="content"></div>`; | ||
document.body.appendChild(tooltipEl); | ||
} | ||
|
||
// Hide if no tooltip | ||
if (tooltipModel.opacity === 0) { | ||
tooltipEl.style.opacity = "0"; | ||
return; | ||
} | ||
|
||
// Set Text | ||
if (tooltipModel.body) { | ||
const { label, value } = tooltipModel.dataPoints[0]; | ||
const tooltipContent = tooltipEl.querySelector("div"); | ||
if (tooltipContent) { | ||
let innerHtml = `<div class="value">${value} ms</div>`; | ||
innerHtml += `<div class="label">${label}</div>`; | ||
tooltipContent.innerHTML = innerHtml; | ||
} | ||
} | ||
|
||
// Enable tooltip and set position | ||
const canvas: Element = this._chart.canvas; | ||
const position = canvas.getBoundingClientRect(); | ||
tooltipEl.style.opacity = "1"; | ||
tooltipEl.style.left = | ||
position.left + window.pageXOffset + tooltipModel.caretX + "px"; | ||
tooltipEl.style.top = | ||
position.top + window.pageYOffset + tooltipModel.caretY + "px"; | ||
}; | ||
|
||
const CHART_OPTION: ChartOptions = { | ||
tooltips: { | ||
intersect: false, // Show tooltip when cursor in between bars | ||
enabled: false, // Hide default tooltip | ||
custom: CUSTOM_TOOLTIP, | ||
}, | ||
legend: { | ||
display: false, | ||
}, | ||
scales: { | ||
xAxes: [ | ||
{ | ||
ticks: { | ||
display: false, | ||
}, | ||
gridLines: { | ||
display: false, | ||
}, | ||
}, | ||
], | ||
yAxes: [ | ||
{ | ||
ticks: { | ||
stepSize: 100, | ||
fontSize: 10, | ||
fontColor: "#EEE", | ||
beginAtZero: true, | ||
display: true, | ||
}, | ||
gridLines: { | ||
display: false, | ||
}, | ||
}, | ||
], | ||
}, | ||
animation: { | ||
duration: 0, // general animation time | ||
}, | ||
hover: { | ||
animationDuration: 0, // duration of animations when hovering an item | ||
}, | ||
responsiveAnimationDuration: 0, // animation duration after a resize | ||
}; | ||
|
||
function PingBarChart({ pingInfo }: { pingInfo: PingRollupInfo }) { | ||
const [series, setSeries] = React.useState<Series>("short"); | ||
const seriesData = pingInfo[series] || []; | ||
|
||
const seriesLength = seriesData.length; | ||
const chartData: Chart.ChartData = { | ||
labels: seriesData.map((val, i) => { | ||
return ` | ||
<p class="mb-0">${val.confirmed} of ${val.submitted} confirmed</p> | ||
${ | ||
val.loss | ||
? `<p class="mb-0">${val.loss.toLocaleString(undefined, { | ||
style: "percent", | ||
minimumFractionDigits: 2, | ||
})} loss</p>` | ||
: "" | ||
} | ||
${SERIES_INFO[series].label(seriesLength - i)}min ago | ||
`; | ||
}), | ||
datasets: [ | ||
{ | ||
backgroundColor: seriesData.map((val) => | ||
val.loss ? "#fa62fc" : "#00D192" | ||
), | ||
hoverBackgroundColor: seriesData.map((val) => | ||
val.loss ? "#fa62fc" : "#00D192" | ||
), | ||
borderWidth: 0, | ||
data: seriesData.map((val) => val.mean || 0), | ||
}, | ||
], | ||
}; | ||
|
||
return ( | ||
<div className="card-body py-3"> | ||
<div className="align-box-row align-items-start justify-content-between"> | ||
<div className="d-flex justify-content-between w-100"> | ||
<span className="mb-0 font-size-sm">Average Confirmation Time</span> | ||
|
||
<div className="font-size-sm"> | ||
{SERIES.map((key) => ( | ||
<button | ||
key={key} | ||
onClick={() => setSeries(key)} | ||
className={classNames("btn btn-sm btn-white ms-2", { | ||
active: series === key, | ||
})} | ||
> | ||
{SERIES_INFO[key].interval} | ||
</button> | ||
))} | ||
</div> | ||
</div> | ||
|
||
<div | ||
id="perf-history" | ||
className="mt-3 d-flex justify-content-end flex-row w-100" | ||
> | ||
<div className="w-100"> | ||
<Bar data={chartData} options={CHART_OPTION} height={80} /> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.