100}
+ class:unlucky={luckPct > 0 && luckPct < 100}
+ title="Luck = best share / total work for this worker. 100% means your best share matches the work done. Above 100% is lucky, below is unlucky."
+ >
+ {luckLabel}
+ luck
+
+
+
+
+
Total work
+
{formatWork(workerHashes)}
+
+ {workShare.toFixed(1)}% of pool ·
+ last share {formatAgo(lastShare)}
+
+
+
+
+
+
Session
+ {#if !online}
+
Worker is offline. Last seen {formatAgo(lastShare)}.
+ {:else}
+
+
+ Session best
+ {formatDifficulty(bestSession)}
+
+
+ Current diff
+ {formatDifficulty(client?.diff ?? 0)}
+
+
+ Min diff
+ {formatDifficulty(worker?.mindiff ?? 0)}
+
+ {#if client?.useragent}
+
+ User agent
+ {client.useragent}
+
+ {/if}
+
+ {/if}
+
+
+
+
diff --git a/ui/src/stores/selection.svelte.ts b/ui/src/stores/selection.svelte.ts
index c6be297..6c1443c 100644
--- a/ui/src/stores/selection.svelte.ts
+++ b/ui/src/stores/selection.svelte.ts
@@ -1,30 +1,38 @@
-// Simple hash-based routing for user pages. The URL hash is the
+// Simple hash-based routing for detail pages. The URL hash is the
// source of truth:
-// #/ -> dashboard
-// #/user/ -> per-user page
+// #/ -> dashboard
+// #/user/ -> per-user page
+// #/worker/ -> per-worker page
//
-// selectUser / clearSelection just mutate the hash; a hashchange
-// listener reads it back into reactive state so any component in the
-// tree re-renders when the route changes.
+// selectUser / selectWorker / clearSelection just mutate the hash;
+// a hashchange listener reads it back into reactive state so any
+// component in the tree re-renders when the route changes.
const USER_PREFIX = "#/user/";
+const WORKER_PREFIX = "#/worker/";
-export const selection = $state<{ user: string | null }>({
- user: readHash(),
-});
+type Selection = { user: string | null; worker: string | null };
-function readHash(): string | null {
- if (typeof window === "undefined") return null;
+export const selection = $state(readHash());
+
+function readHash(): Selection {
+ if (typeof window === "undefined") return { user: null, worker: null };
const h = window.location.hash;
- if (h.startsWith(USER_PREFIX)) {
- const addr = decodeURIComponent(h.slice(USER_PREFIX.length));
- return addr || null;
+ if (h.startsWith(WORKER_PREFIX)) {
+ const w = decodeURIComponent(h.slice(WORKER_PREFIX.length));
+ return { user: null, worker: w || null };
}
- return null;
+ if (h.startsWith(USER_PREFIX)) {
+ const u = decodeURIComponent(h.slice(USER_PREFIX.length));
+ return { user: u || null, worker: null };
+ }
+ return { user: null, worker: null };
}
function syncFromHash(): void {
- selection.user = readHash();
+ const parsed = readHash();
+ selection.user = parsed.user;
+ selection.worker = parsed.worker;
}
if (typeof window !== "undefined") {
@@ -32,16 +40,19 @@ if (typeof window !== "undefined") {
}
export function selectUser(address: string): void {
- // Set the hash; the hashchange listener updates selection.user so
- // routing-style state stays in one place.
window.location.hash = USER_PREFIX + encodeURIComponent(address);
}
+export function selectWorker(workername: string): void {
+ window.location.hash = WORKER_PREFIX + encodeURIComponent(workername);
+}
+
export function clearSelection(): void {
- // Prefer history.back when we arrived here via selectUser so the
- // browser back arrow does the obvious thing; otherwise just drop
- // the hash so a fresh page load starts on the dashboard.
- if (window.history.length > 1 && window.location.hash.startsWith(USER_PREFIX)) {
+ if (
+ window.history.length > 1 &&
+ (window.location.hash.startsWith(USER_PREFIX) ||
+ window.location.hash.startsWith(WORKER_PREFIX))
+ ) {
window.history.back();
} else {
window.location.hash = "";
diff --git a/ui/src/types.ts b/ui/src/types.ts
index f257504..100cc00 100644
--- a/ui/src/types.ts
+++ b/ui/src/types.ts
@@ -44,6 +44,7 @@ export type Worker = {
lastshare: number;
bestdiff: number;
bestever: number;
+ shares: number;
mindiff: number;
idle: boolean;
};