consensus/cmd/web/views/miner.qtpl
DataHoarder 55661a12da
All checks were successful
continuous-integration/drone/push Build is passing
WIP: Bootstrap-based responsive interface, CSS only
2024-03-20 13:37:26 +01:00

344 lines
16 KiB
Plaintext

{% import "git.gammaspectra.live/P2Pool/p2pool-observer/cmd/index" %}
{% import "git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain" %}
{% import p2pooltypes "git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/types" %}
{% import cmdutils "git.gammaspectra.live/P2Pool/p2pool-observer/cmd/utils" %}
{% import "fmt" %}
{% import (
_ "embed"
) %}
{% code
//go:embed "css/miner.css"
var minerCssContent string
%}
{% code
type MinerPage struct {
// inherit from base page, so its' title is used in error page.
BasePage
Refresh int
Positions struct {
Resolution int
ResolutionWindow int
SeparatorIndex int
Blocks *cmdutils.PositionChart
Uncles *cmdutils.PositionChart
BlocksInWindow *cmdutils.PositionChart
UnclesInWindow *cmdutils.PositionChart
Payouts *cmdutils.PositionChart
}
ExpectedRewardPerWindow uint64
ExpectedRewardPerDay uint64
WindowWeight uint64
Weight uint64
Miner *cmdutils.MinerInfoResult
LastPoolBlock *sidechain.PoolBlock
DayShares []*index.SideBlock
DaySharesEfforts []float64
LastShares []*index.SideBlock
LastSharesEfforts []float64
LastOrphanedShares []*index.SideBlock
LastFound []*index.FoundBlock
LastPayouts []*index.Payout
LastSweeps <-chan *index.MainLikelySweepTransaction
HashrateSubmit bool
HashrateLocal float64
MagnitudeLocal float64
}
func (p *MinerPage) IsRefresh() (ok, isRefresh bool, interval int, uriRefresh, uriStatic string) {
baseAddress := string(p.Miner.Address.ToBase58())
return true, p.Refresh > 0, p.Refresh, fmt.Sprintf("/miner/%s?refresh", baseAddress), fmt.Sprintf("/miner/%s", baseAddress)
}
func (p *MinerPage) Name() string {
return "miner"
}
%}
{% func (p *MinerPage) Style() %}
{%= p.BasePage.Style() %}
{%s= minerCssContent %}
{% endfunc %}
{% func (p *MinerPage) Title() %}
{%= p.BasePage.Title() %} - Miner {%z= p.Miner.Address.ToBase58() %}
{% endfunc %}
{% func (p *MinerPage) Content() %}
<div class="btn-group btn-group-sm page-tab" role="group">
<a href="#page-miner" class="btn p-1 btn-outline-secondary">Miner</a>
<a href="#page-payouts" class="btn p-1 btn-outline-secondary">Payouts</a>
<a href="#page-shares" class="btn p-1 btn-outline-secondary">Shares</a>
{% if len(p.LastFound) > 0 %}
<a href="#page-blocks" class="btn p-1 btn-outline-secondary">Blocks</a>
{% endif %}
<a href="#page-sweeps" class="btn p-1 btn-outline-secondary">Sweeps</a>
<a href="/miner-options/{%z= p.Miner.Address.ToBase58() %}" class="btn btn-outline-secondary">Options</a>
</div>
<div id="page-miner" class="my-3">
<div style="text-align: center">
{% if p.Miner.LastShareTimestamp == 0 %}
<div class="alert alert-warning" role="alert">
<h3>No shares have been reported to this P2Pool network in the past for this miner.</h3>
<p>Finding shares is a random process based on your hashrate vs this P2Pool hashrate. This can take several days for low hashrate miners, and depending on your luck.</p>
<p>Please use the Share Time Calculator below to find your average share time (with average luck).</p>
<p>You can also verify you are reachable by opening ports with the checker below.</p>
<br/>
<a class="btn btn-lg btn-primary text-white" href="/calculate-share-time">Share Time Calculator</a>
<a class="btn btn-lg btn-primary text-white" href="/connectivity-check">Connectivity Check</a>
</div>
{% endif %}
{% if p.LastPoolBlock != nil && p.LastPoolBlock.ShareVersion() < sidechain.ShareVersion_V2 && p.LastPoolBlock.ShareVersionSignaling() != sidechain.ShareVersion_V2 %}
<div class="alert alert-danger" role="alert">
<h3 title="Share version {%s p.LastPoolBlock.ShareVersion().String() %}, signaling {%s p.LastPoolBlock.ShareVersionSignaling().String() %}">Recent shares indicate you are running an outdated version of P2Pool</h3>
<p>A new version of <a href="https://github.com/SChernykh/p2pool/releases/tag/v3.3">P2Pool (v3.0+)</a> has been released with several improvements, which requires a consensus change.</p>
<p>P2Pool (not Monero!) has hardforked to new consensus rules on <strong>March 18th, 2023 at 21:00 UTC</strong>. All versions before P2Pool v3.0 are incompatible. P2Pool v3.3+ is recommended.</p>
<p>If you keep using previous versions, you will keep mining as usual, but become almost a solo miner, as incompatible clients will mine on their own.</p>
<p>{% if p.Context().NetServiceAddress == "p2pool.observer" %}After the fork, you can check on <a href="{%s p.Context().GetUrl("old.p2pool.observer") %}/miner/{%z= p.Miner.Address.ToBase58() %}">OLD.P2Pool.Observer</a>.{% elseif p.Context().NetServiceAddress == "mini.p2pool.observer" %}After the fork, you can check on <a href="https://{%s p.Context().GetUrl("old-mini.p2pool.observer") %}/miner/{%z= p.Miner.Address.ToBase58() %}">OLD-MINI.P2Pool.Observer</a>.{% else %}Please check on an observer tracking the old chain.{% endif %}</p>
<p>After upgrading to a supported P2Pool version and mining a share, this message will be dismissed.</p>
</div>
<hr/>
{% elseif p.LastPoolBlock != nil && p.LastPoolBlock.ShareVersion() > sidechain.ShareVersion_V1 && p.LastPoolBlock.Side.ExtraBuffer.SoftwareId == p2pooltypes.SoftwareIdP2Pool && p.LastPoolBlock.Side.ExtraBuffer.SoftwareVersion.String() != p.Context().Pool.Versions.P2Pool.ShortVersion().String() %}
<div class="alert alert-warning" role="alert">
<h3>Recent shares indicate you are running an older version of P2Pool</h3>
<p><a href="{%s p.Context().Pool.Versions.P2Pool.Link %}">P2Pool {%s p.Context().Pool.Versions.P2Pool.Version %}</a> has been released.</p>
<p>Your most recent share indicates are currently running {%= software_info(p.LastPoolBlock.Side.ExtraBuffer.SoftwareId, p.LastPoolBlock.Side.ExtraBuffer.SoftwareVersion) %}. It is recommended to upgrade.</p>
<p>After upgrading to this P2Pool version and mining a share, this message will be dismissed.</p>
</div>
<hr/>
{% endif %}
<p><strong>Payout Address:</strong> <span class="mono small">{%z= p.Miner.Address.ToBase58() %}</span></p>
{% if p.Miner.Alias != "" %}
<p><strong>Miner Alias:</strong> <span class="mono">{%s p.Miner.Alias %}</span></p>
<p><small>Miner Alias is user content and not verified. This value should only be used for vanity purposes.</small></p>
{% endif %}
<p><small><a href="/miner-options/{%z= p.Miner.Address.ToBase58() %}#mineralias">[Change Miner Alias]</a> :: <a href="/miner-options/{%z= p.Miner.Address.ToBase58() %}#webhooks">[Configure WebHook notifications]</a></small></p>
<table class="center" style="max-width: calc(15em + 15em + 15em + 15em + 15em)">
<tr>
<th>Last Share</th>
<th>Current Shares</th>
<th>Estimated Hashrate</th>
<th>Pool Share %</th>
<th>Estimated Window Reward</th>
</tr>
<tr>
<td title="{%s utc_date(p.Miner.LastShareTimestamp) %}">{%s time_elapsed_short(p.Miner.LastShareTimestamp) %}</td>
<td>{%dul p.Positions.BlocksInWindow.Total() %} blocks (+{%dul p.Positions.UnclesInWindow.Total() %} uncles)</td>
{% code windowWeightRatio := float64(p.WindowWeight) / float64(p.Context().Pool.SideChain.Window.Weight.Lo) %}
<td>{%s si_units(windowWeightRatio * float64(diff_hashrate(p.Context().Pool.SideChain.LastBlock.Difficulty, p.Context().Consensus.TargetBlockTime)), 3) %}H/s</td>
<td>{%f.3 windowWeightRatio*100 %}%</td>
<td>{%s monero_to_xmr(p.ExpectedRewardPerWindow) %} XMR</td>
</tr>
<tr><td colspan="5">&nbsp;</td></tr>
<tr>
<th>Estimated Total Shares</th>
<th>Day Shares</th>
<th>Day Hashrate</th>
<th>Day Share %</th>
<th>Estimated Daily Reward</th>
</tr>
<tr>
<td>{% if p.LastPoolBlock != nil %}Around {%dul p.Miner.Shares[1].ShareCount %} blocks (+{%dul p.Miner.Shares[1].UncleCount %} uncles{% if p.Miner.Shares[0].ShareCount > 0 %}, +{%dul p.Miner.Shares[0].ShareCount %} orphans{% endif %}){% else %}No shares reported{% endif %}</td>
<td>{%dul p.Positions.Blocks.Total() %} blocks (+{%dul p.Positions.Uncles.Total() %} uncles)</td>
{% code weightRatio := (float64(p.Weight) / (float64(p.Context().Pool.SideChain.Window.Weight.Mul64(4).Lo) * (float64(p.Context().Pool.SideChain.Consensus.ChainWindowSize) / float64(p.Context().Pool.SideChain.Window.Blocks)))) %}
<td>{%s si_units(weightRatio * float64(diff_hashrate(p.Context().Pool.SideChain.LastBlock.Difficulty, p.Context().Consensus.TargetBlockTime)), 3) %}H/s</td>
<td>{%f.3 weightRatio*100 %}%</td>
<td>{%s monero_to_xmr(p.ExpectedRewardPerDay) %} XMR</td>
</tr>
</table>
</div>
<div class="container-fluid my-3 p-2 bg-body-tertiary border rounded-2">
<div class="row row-cols-1 row-cols-xxl-12">
<div class="col col-xxl-8 mb-2">
<h3>Shares during last day</h3>
{% if p.HashrateSubmit || (p.Positions.Blocks.Total() + p.Positions.Uncles.Total()) > 2 %}
{% code dividersX, dividersY, points := NewSharesPositionChart(p.Context(), p.DayShares, &p.LastPayouts, &p.DaySharesEfforts, DefaultBlocksPositionChartDuration) %}
{%= TemplatePositionGraph(dividersX, dividersY, points, 120, "24 hours ago", "Now") %}
{% else %}
{% code dividersX, dividersY, points := NewSharesPositionChart(p.Context(), p.DayShares, &p.LastPayouts, nil, DefaultBlocksPositionChartDuration) %}
{%= TemplatePositionGraph(dividersX, dividersY, points, 120, "24 hours ago", "Now") %}
{% endif %}
</div>
<div class="col col-xxl-4 mb-2">
<h3>Shares in PPLNS window</h3>
<code class="position-chart">{%s p.Positions.BlocksInWindow.String() %}</code>
<br/>
<code class="position-chart">{%s p.Positions.UnclesInWindow.String() %}</code>
</div>
<div class="col col-xxl-12 mb-2">
<div class="alert alert-secondary small mt-2">
Shares appear on the right, and get older towards the left.
<br/>
Uncle shares are displayed as squares on the graph.
<br/>
Payouts are displayed via dashed vertical lines on the graph.
<br/>
Shares within the PPLNS window will be weighted towards receiving a payout when any Monero block is found by any P2Pool miner.
</div>
</div>
</div>
</div>
{% if p.Miner.LastShareTimestamp != 0 %}
<div class="my-3" style="text-align: center">
<form action="/calculate-share-time" method="get">
<h3>Effort Calculation</h3>
<p>Local hashrate of each P2Pool miner is not known by the network. A guess is calculated based on daily estimation. If you provide a value here, it will be more accurate for effort calculation.</p>
<p>This data will not be saved.</p>
<label for="hashrate_local">Your Local Hashrate</label><br/>
<input type="number" name="hashrate" id="hashrate_local" placeholder="100" size="8" class="mono" value="{% if p.HashrateLocal > 0 %}{%s str(p.HashrateLocal) %}{% endif %}"/>
<select name="magnitude">
<option value="1"{% if p.MagnitudeLocal == 1 %} selected{% endif %}>H/s</option>
<option value="1000"{% if p.MagnitudeLocal == 1000 %} selected{% endif %}>KH/s</option>
<option value="1000000"{% if p.MagnitudeLocal == 1000000 %} selected{% endif %}>MH/s</option>
<option value="1000000000"{% if p.MagnitudeLocal == 1000000000 %} selected{% endif %}>GH/s</option>
</select>
<br/>
<input type="submit" value="Calculate" style="width: 20em; margin: 20px;"/>
</form>
</div>
{% endif %}
</div>
<div id="page-payouts" class="container-fluid my-3 p-2 bg-body-tertiary border rounded-2 text-center" tabindex="-1">
<div class="row align-items-start">
<div class="col">
<h2>Most recent payouts</h2>
</div>
</div>
<div class="row">
<div class="col col-12">
{% if len(p.LastPayouts) == 0 %}
No payouts have been reported.
{% else %}
{%= TemplatePayoutsSlice(p.Context(), slice_maxSize(p.LastPayouts, 12)) %}
{% endif %}
</div>
<div class="col col-12 mt-3">
<div class="alert alert-secondary small">
Payouts happen when a Monero block is found by P2Pool miners and shares are in the PPLNS window.
<br/>
There is no balance in P2Pool. All rewards are paid out instantly to everyone as part of the mining process.
</div>
</div>
<div class="col my-3 col-12">
<a href="/payouts/{%z= p.Miner.Address.ToBase58() %}" class="btn btn-lg btn-primary">
Payout History
</a>
</div>
</div>
</div>
<div id="page-shares">
<div class="container-fluid my-3 p-2 bg-body-tertiary border rounded-2 text-center" tabindex="-1">
<div class="row align-items-start">
<div class="col">
<h2>Most recent shares</h2>
</div>
</div>
<div class="row">
<div class="col col-12">
{% if len(p.LastShares) == 0 %}
No shares have been reported to P2Pool network.
{% else %}
{% if p.HashrateSubmit || (p.Positions.Blocks.Total() + p.Positions.Uncles.Total()) > 2 %}
{%= TemplateShares(p.Context(), p.LastShares, true, &p.LastSharesEfforts) %}
{% else %}
{%= TemplateShares(p.Context(), p.LastShares, true, nil) %}
{% endif %}
{% endif %}
</div>
</div>
<div class="col col-12 mt-3">
<div class="alert alert-secondary small">
Shares are listed here when they are accepted by the P2Pool network.
<br/>
Finding shares is a random, memory-less process, and effort may vary depending on your miner luck.
</div>
</div>
</div>
{% if len(p.LastOrphanedShares) > 0 %}
<div class="container-fluid my-3 p-2 bg-body-tertiary border rounded-2 text-center" tabindex="-1">
<div class="row align-items-start">
<div class="col">
<h2>Most recent orphaned shares</h2>
</div>
</div>
<div class="row">
<div class="col col-12">
{%= TemplateShares(p.Context(), p.LastOrphanedShares, true, nil) %}
</div>
</div>
</div>
{% endif %}
</div>
{% if len(p.LastFound) > 0 %}
<div id="page-blocks" class="container-fluid my-3 p-2 bg-body-tertiary border rounded-2 text-center" tabindex="-1">
<div class="row align-items-start">
<div class="col">
<h2>Most recent Monero blocks found</h2>
</div>
</div>
<div class="row">
<div class="col col-12">
{%= TemplateFoundBlocks(p.Context(), p.LastFound, true) %}
</div>
<div class="col my-3 col-12">
<a href="/blocks?miner={%z= p.Miner.Address.ToBase58() %}" class="btn btn-lg btn-primary">
More found blocks
</a>
</div>
</div>
</div>
{% endif %}
<div id="page-sweeps" class="container-fluid my-3 p-2 bg-body-tertiary border rounded-2 text-center" tabindex="-1">
<div class="row align-items-start">
<div class="col">
<h2>Most recent likely sweeps</h2>
</div>
</div>
<div class="row">
<div class="col col-12">
{%= TemplateSweeps(p.Context(), p.LastSweeps, true) %}
</div>
<div class="col my-3 col-12">
<a href="/sweeps?miner={%z= p.Miner.Address.ToBase58() %}" class="btn btn-lg btn-primary" tabindex="-1">
More likely sweeps
</a>
</div>
</div>
</div>
{% endfunc %}