Files
KamadoPool/ui/src/App.svelte
T
satoshi 1157f3501a Add best share analysis page, miner column, and UI improvements
- Best share page: hex + binary hash comparison against network target,
  per-bit coloring showing exactly which bits prevented a valid block,
  toggle between network diff at time of finding vs current diff
- Capture best share hash from ckpool logs with one-time backfill
- Persist network difficulty at time of best share for historical accuracy
- Add miner (worker) column to blocks table via coinbase address matching
- Truncate block hashes in table with full hash on hover
- Increase hashrate chart Y-axis to 7 ticks for better readability
2026-05-18 17:19:35 +03:00

145 lines
3.4 KiB
Svelte

<script lang="ts">
import { onMount } from "svelte";
import { connect, snap } from "./stores/snapshot.svelte";
import { selection } from "./stores/selection.svelte";
import Header from "./lib/Header.svelte";
import HealthBanners from "./lib/HealthBanners.svelte";
import PoolOverview from "./lib/PoolOverview.svelte";
import HashrateChart from "./lib/HashrateChart.svelte";
import MinersTable from "./lib/MinersTable.svelte";
import BlocksTable from "./lib/BlocksTable.svelte";
import BestShares from "./lib/BestShares.svelte";
import UserDetailPage from "./lib/UserDetailPage.svelte";
import WorkerDetailPage from "./lib/WorkerDetailPage.svelte";
import AcceleratorPage from "./lib/AcceleratorPage.svelte";
import StatsPage from "./lib/StatsPage.svelte";
import BestSharePage from "./lib/BestSharePage.svelte";
import SharesBar from "./lib/SharesBar.svelte";
import BlockFoundAnimation from "./lib/BlockFoundAnimation.svelte";
onMount(() => {
connect();
});
</script>
<main class:no-bg={selection.page === "accelerator"}>
<Header />
{#if snap.data}
<HealthBanners />
{/if}
{#if !snap.data}
<div class="card placeholder page-enter">
<div class="stat-label">Status</div>
<div class="stat-value">Connecting to kamado-api…</div>
{#if snap.error}
<div class="stat-sub bad">{snap.error}</div>
{/if}
</div>
{:else if selection.page === "accelerator"}
{#key 'accelerator'}
<div class="page-enter">
<AcceleratorPage />
</div>
{/key}
{:else if selection.page === "stats"}
{#key 'stats'}
<div class="page-enter">
<StatsPage />
</div>
{/key}
{:else if selection.page === "bestshare"}
{#key 'bestshare'}
<div class="page-enter">
<BestSharePage />
</div>
{/key}
{:else if selection.worker}
{#key selection.worker}
<div class="page-enter">
<WorkerDetailPage />
</div>
{/key}
{:else if selection.user}
{#key selection.user}
<div class="page-enter">
<UserDetailPage />
</div>
{/key}
{:else}
{#key 'dashboard'}
<div class="page-enter">
<PoolOverview />
<SharesBar />
<HashrateChart />
<BlocksTable />
<BestShares />
<MinersTable />
</div>
{/key}
{/if}
</main>
<BlockFoundAnimation />
<style>
main {
position: relative;
max-width: 1280px;
margin: 0 auto;
padding: 1.5rem 1.25rem 4rem;
display: flex;
flex-direction: column;
gap: 1.25rem;
}
main::before {
content: '';
position: fixed;
inset: 0;
z-index: -1;
background: url('/tanjiro2.jpg') center center / cover no-repeat;
opacity: 0.12;
pointer-events: none;
transition: opacity 0.4s ease;
}
main.no-bg::before {
opacity: 0;
}
main::after {
content: '';
position: fixed;
inset: 0;
z-index: -1;
background: url('/rengoku1.jpg') center center / cover no-repeat;
opacity: 0;
pointer-events: none;
transition: opacity 0.4s ease;
}
main.no-bg::after {
opacity: 0.30;
}
.placeholder {
text-align: center;
}
.bad {
color: var(--bad);
}
.page-enter {
display: flex;
flex-direction: column;
gap: 1.25rem;
animation: page-fade-in 0.3s ease-out;
}
@keyframes page-fade-in {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>