igneum/app/igneum-app/ui/index.html
igneum-labs 057f0ec8bf app: Power control setting (default off): the app never asks for administrator rights on its own
the project lead, 5 October 2026: "if we don't have to ask then don't ask". The NVIDIA power cap and the efficiency sweep need
administrator rights (one UAC prompt); PC 1 raised that prompt for cmd.exe at every app start and every sweep attempt
(17:00, 17:30, 18:12, 19:04 UTC today, each cancelled unanswered after 2 minutes; the "Windows Command Processor"
the project lead saw).

- config.rs: `power_control` (default OFF on every machine); `sweep` default becomes off and is implied by it (an
  install carrying sweep = true without power_control is migrated to off on load).
- engine.rs: `elevation_allowed(power_control, sweep_only)` gates the power cap (`power_cap_plan` builds nothing when
  off, the card note says so), the sweep scheduler, Sweep now, the sweep helper; no prompt on quit (the limits reset at
  the next reboot); no second prompt through PowerShell when the window host's prompt goes unanswered.
  Cmd::PowerControl(on): on = ONE prompt at that moment (every NVIDIA cap in one step), off = nothing asks;
  `power_control_after_prompt` turns a refused, cancelled or unanswered prompt into "power control off:
  administrator rights were not given" (switch back off, sweep off, no retries). Unit tests: off builds no elevated
  command; on + refusal gives the notice; rights given keeps it on.
- platform.rs: `elevated_failure` maps the launcher's exit 251 and the "canceled" wording to the prompt, any other
  code to the step itself.
- server.rs: POST /api/power/control {on}. ui: the Power control switch with the line "Windows asks for administrator
  rights once; the cap and the sweep need them", the note beside it, the sweep switch disabled while it is off.
- The clock-sync prompt stays behind the Sync clock button only (unchanged).
- tools/windows/power-prompts-off.ps1: the 0.3.9 job that switched PC 1's sweep off through the API it has
  (run-20261005-192313: sweep True -> False; the 0.3.9 cap has no off switch, it asks at an app start only).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-05 19:25:12 +00:00

390 lines
23 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Igneum Miner</title>
<meta name="color-scheme" content="dark">
<link rel="icon" href="mark.svg" type="image/svg+xml">
<link rel="stylesheet" href="app.css">
</head>
<body class="phase-welcome" data-phase="welcome">
<header class="top">
<div class="brand">
<img src="mark.svg" width="30" height="30" alt="">
<span class="word">IGNEUM</span><span class="miner">MINER</span>
</div>
<div class="top-right">
<div class="pill" id="pill"><span class="dot"></span><span id="pill-text">starting</span></div>
<button class="icon-btn" id="btn-settings" title="Settings" aria-label="Settings" hidden>
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3h.1a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8v.1a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"></path></svg>
</button>
</div>
</header>
<!-- the notice strip: one notice at a time (updates, remote jobs, the clock), the most important first; app.js fills
it from the state and the content below moves once when it appears or goes. The close control hides a notice
until its state moves on. -->
<div class="notices" id="notices" hidden>
<div class="notice" id="notice" role="status" aria-live="polite">
<span class="notice-text" id="notice-text"></span>
<span class="notice-actions" id="notice-actions"></span>
<button class="notice-close" id="notice-close" data-act="close" title="Close" aria-label="Close">&times;</button>
<span class="notice-detail mono" id="notice-detail" hidden></span>
<span class="prog" id="notice-prog" hidden><i></i></span>
</div>
</div>
<main id="main">
<!-- 1. welcome -->
<section class="screen" id="screen-welcome">
<div class="hero">
<div class="coin-wrap"><img class="coin" src="coin.png" width="148" height="148" alt=""></div>
<div class="eyebrow ember">devnet v4 &middot; nothing is bought or sold</div>
<h1>Igneum Miner</h1>
<p class="lead">Mined by GPUs. Proven by fire. This app runs a node on this machine and mines Igneum with your graphics card.</p>
<div class="three">
<div class="tile">
<div class="k">01</div>
<div class="t">Runs a node</div>
<div class="s">Syncs the chain from the seed node, usually under a minute.</div>
</div>
<div class="tile">
<div class="k">02</div>
<div class="t">Mines on your GPU</div>
<div class="s">A new program every hour. The next one compiles while you mine.</div>
</div>
<div class="tile">
<div class="k">03</div>
<div class="t">Pays your address</div>
<div class="s">Block rewards go to an EVM address you choose or one made here.</div>
</div>
</div>
<div class="cta">
<button class="btn primary big" id="btn-begin">Get started</button>
</div>
<p class="seedline mono">Nobody from Igneum will ever ask for your seed.</p>
</div>
</section>
<!-- 2. cards -->
<section class="screen" id="screen-cards">
<div class="step">
<div class="eyebrow">step 1 of 2</div>
<h2>Your GPU</h2>
<p class="sub" id="cards-sub">Asking the graphics cards to report in.</p>
<div class="cards" id="cards-list">
<div class="card detecting" id="cards-detecting">
<div class="spinner"></div>
<div>
<div class="t">Detecting</div>
<div class="s mono" id="detect-text">loading the Metal worker</div>
</div>
</div>
</div>
<p class="note" id="cards-note" hidden></p>
<p class="note" id="cards-power" hidden>Igneum caps each NVIDIA card's power at 80% of its default limit to keep it stable (an RTX 5090 at full power hard-crashed in the field). This needs administrator rights once, when mining starts; the limit goes back to what it was on quit. The slider sets the cap per card. In the first hour, and then weekly, the efficiency sweep steps the cap from 100% down to 50% on the live program and holds the best MH per watt; a cap you set by hand stays pinned.</p>
<p class="note" id="cards-help" hidden>Each card you switch on gets its own worker. Identities take turns on the card and each one votes and pays separately; 8 suits a big card, 2 a small one, 1 an integrated GPU.</p>
<div class="cta">
<button class="btn primary" id="btn-cards-next" disabled>Continue</button>
<button class="btn ghost" id="btn-cards-retry" hidden>Detect again</button>
</div>
</div>
</section>
<!-- 3. address -->
<section class="screen" id="screen-address">
<div class="step">
<div class="eyebrow">step 2 of 2</div>
<h2>Where should rewards go?</h2>
<p class="sub">Every block this machine finds pays one EVM address. Pick one way. One block in 100 pays the miner software's dev fee; Settings turns it off.</p>
<div class="options">
<label class="option on" id="opt-generate">
<input type="radio" name="mode" value="generate" checked>
<span class="radio"></span>
<span class="body">
<span class="t">Make me an address <span class="tag">recommended</span></span>
<span class="s">A secp256k1 key is made on this machine and stored in the app folder with the file locked to your user. You see the key once, to save it.</span>
</span>
</label>
<label class="option" id="opt-paste">
<input type="radio" name="mode" value="paste">
<span class="radio"></span>
<span class="body">
<span class="t">Use my own address</span>
<span class="s">Paste an EVM address you control. 0x followed by 40 hex characters.</span>
<input type="text" class="addr-input mono" id="addr-input" placeholder="0x" spellcheck="false" autocomplete="off">
<span class="err" id="addr-err" hidden>That is not an EVM address. 0x followed by 40 hex characters.</span>
</span>
</label>
</div>
<p class="err" id="start-blocked" hidden></p>
<div class="cta">
<button class="btn primary big" id="btn-start">Start mining</button>
<button class="btn ghost" id="btn-address-back">Back</button>
</div>
</div>
</section>
<!-- 4. dashboard -->
<section class="screen" id="screen-dashboard">
<div class="strip">
<div class="cell big ember">
<div class="k">hash rate</div>
<div class="v"><span id="d-hash">0.0</span><span class="unit">MH/s</span></div>
<div class="s" id="d-hash-sub">waiting for the worker</div>
</div>
<div class="cell big">
<div class="k">blocks found</div>
<div class="v" id="d-blocks">0</div>
<div class="s" id="d-blocks-sub">accepted by the node</div>
</div>
<div class="cell big" id="d-node-cell">
<div class="k">node</div>
<div class="v state" id="d-node">starting</div>
<div class="s" id="d-node-sub">opening the database</div>
</div>
<div class="cell big">
<div class="k">next program</div>
<div class="v" id="d-eta">--:--</div>
<div class="s" id="d-eta-sub">waiting for the node</div>
</div>
</div>
<div class="grid">
<div class="col-main">
<div class="card">
<div class="card-head">
<div class="eyebrow"><span class="dot small"></span>your blocks</div>
<div class="stats mono"><span>last 10 min <b id="d-found-10">0</b></span><span>last hour <b id="d-found-60">0</b></span><span>dev fee <b id="d-fee">0</b></span><span>chain <b id="d-chain-blocks">0</b></span></div>
</div>
<canvas id="dag" aria-hidden="true"></canvas>
<div class="legend"><span><i class="sw ember"></i>block this machine found</span><span><i class="sw glow"></i>just accepted</span><span><i class="sw line"></i>one minute</span></div>
</div>
<div class="card">
<div class="card-head"><h3>Cards</h3><div class="eyebrow" id="d-cards-eyebrow">1 worker</div></div>
<div class="gpu-tiles" id="d-cards"><div class="empty">No card yet.</div></div>
<div class="gpu-off mono" id="d-cards-off" hidden></div>
</div>
</div>
<div class="col-side">
<div class="card">
<div class="card-head"><h3>Node</h3><div class="eyebrow" id="d-node-net">devnet v4</div></div>
<div class="kv">
<div><span class="k">height</span><span class="v mono" id="n-blocks">0</span></div>
<div><span class="k">headers</span><span class="v mono" id="n-headers">0</span></div>
<div><span class="k">peers</span><span class="v mono" id="n-peers">0</span></div>
<div><span class="k">daa score</span><span class="v mono" id="n-daa">0</span></div>
<div><span class="k">difficulty</span><span class="v mono" id="n-diff">0</span></div>
<div><span class="k">tips</span><span class="v mono" id="n-tips">0</span></div>
</div>
<div class="clock-card" id="n-clock" hidden>
<p class="clock-msg" id="n-clock-msg"></p>
<div class="row"><button class="btn small primary" id="n-clock-sync">Sync clock</button><span class="note mono small" id="n-clock-result"></span></div>
<p class="note" id="n-clock-hint"></p>
</div>
<p class="note" id="n-note"></p>
</div>
<div class="card">
<div class="card-head"><h3>Finality</h3><div class="eyebrow">miner-only</div></div>
<div class="kv">
<div><span class="k">last lock</span><span class="v mono" id="f-lock">none yet</span></div>
<div><span class="k">age</span><span class="v mono" id="f-age">n/a</span></div>
<div><span class="k">votes sent</span><span class="v mono" id="f-votes">0</span></div>
</div>
<p class="note" id="f-note">Locks appear once the miner votes on checkpoints.</p>
</div>
<div class="tile" id="tile-proving">
<div class="h">Proving</div>
<div class="kv">
<div><span class="k">state</span><span class="v mono" id="pv-state">off</span></div>
<div><span class="k">assigned</span><span class="v mono" id="pv-assigned">0</span></div>
<div><span class="k">submitted</span><span class="v mono" id="pv-submitted">0</span></div>
<div><span class="k">paid</span><span class="v mono" id="pv-paid">0</span></div>
<div><span class="k">verifier</span><span class="v mono" id="pv-verifier">not read yet</span></div>
</div>
<p class="note" id="pv-note">Off. Settings switches it on: this machine proves the shards the chain assigns to its keys.</p>
<p class="note" id="pv-verifier-note"></p>
<div class="row" id="pv-setup-row" hidden><button class="btn small" id="pv-setup">Set up</button></div>
</div>
<div class="card">
<div class="card-head"><h3>Events</h3><div class="eyebrow">newest first</div></div>
<div class="feed" id="d-events"><div class="empty">No events yet.</div></div>
</div>
</div>
</div>
</section>
</main>
<!-- the update card (app.js, UpdateCard): one update, centred; Later, Escape or the backdrop leaves the strip above -->
<div class="upd-wrap" id="upd" hidden>
<div class="upd-card" id="upd-card" role="dialog" aria-modal="true" aria-labelledby="upd-name" aria-describedby="upd-line" tabindex="-1">
<div class="upd-mark" id="upd-mark">
<svg class="upd-ring" viewBox="0 0 96 96" aria-hidden="true"><circle class="track" cx="48" cy="48" r="44"></circle><circle class="arc" id="upd-arc" cx="48" cy="48" r="44"></circle></svg>
<img src="mark.svg" width="40" height="40" alt="">
<span class="upd-pct mono" id="upd-pct" hidden></span>
</div>
<div class="eyebrow ember">update</div>
<h2 class="upd-name" id="upd-name"></h2>
<p class="upd-line" id="upd-line"></p>
<p class="upd-cause mono" id="upd-cause" hidden></p>
<ul class="upd-notes" id="upd-notes"></ul>
<button class="upd-more" id="upd-more" type="button" aria-expanded="false" hidden>What changed</button>
<ul class="upd-all" id="upd-all" hidden></ul>
<p class="upd-meta mono" id="upd-meta"></p>
<div class="upd-actions" id="upd-actions"></div>
</div>
</div>
<!-- the key sheet, shown once -->
<div class="sheet-wrap" id="sheet-key" hidden>
<div class="sheet">
<div class="eyebrow ember">shown once</div>
<h2>Save your key</h2>
<p class="sub">This key controls the rewards address. Keep a copy somewhere safe. Anyone with the key can spend what the address holds.</p>
<div class="field">
<div class="k">rewards address</div>
<div class="box mono"><span id="key-address"></span><button class="btn tiny" data-copy="key-address">Copy</button></div>
</div>
<div class="field">
<div class="k">private key</div>
<div class="box mono key"><span id="key-private"></span><button class="btn tiny" data-copy="key-private">Copy</button></div>
</div>
<p class="note">Stored at <span class="mono" id="key-file"></span>, readable by your user only. Settings can show it again.</p>
<p class="note">On devnet the vote keys are test keys derived from the miner's label. Mainnet vote keys will be random and stored like this wallet.</p>
<label class="check"><input type="checkbox" id="key-ack"><span>I have saved my key</span></label>
<div class="cta">
<button class="btn primary big" id="btn-key-done" disabled>Start mining</button>
</div>
</div>
</div>
<!-- settings -->
<div class="panel-wrap" id="settings" hidden>
<aside class="panel">
<div class="panel-head"><h3>Settings</h3><button class="icon-btn" id="btn-settings-close" aria-label="Close">&times;</button></div>
<div class="panel-body">
<div class="field">
<div class="k">rewards address</div>
<div class="box mono"><span id="s-address"></span><button class="btn tiny" data-copy="s-address">Copy</button></div>
<div class="row">
<input type="text" class="addr-input mono" id="s-address-input" placeholder="paste a new address" spellcheck="false" autocomplete="off">
<button class="btn small" id="s-address-save">Change</button>
</div>
<div class="err" id="s-address-err" hidden></div>
<button class="btn small ghost" id="s-reveal" hidden>Show my key</button>
<div class="box mono key" id="s-key-box" hidden><span id="s-key"></span><button class="btn tiny" data-copy="s-key">Copy</button></div>
<label class="switch"><input type="checkbox" id="s-devfee"><span class="track"></span><span id="s-devfee-text">Dev fee 1% (1 block in 100)</span></label>
<p class="note" id="s-devfee-note">The miner software's fee, the same way every GPU miner takes one. The protocol takes none. This switch turns it off.</p>
</div>
<div class="field">
<div class="k">this machine</div>
<div class="row">
<input type="text" class="addr-input" id="s-name" placeholder="a name for this machine" maxlength="40" spellcheck="false">
<button class="btn small" id="s-name-save">Rename</button>
</div>
<p class="note">A label for you only. Keys and labels come from the machine id <span class="mono" id="s-mid"></span>, never from the computer name.</p>
</div>
<div class="field">
<div class="k">cards</div>
<div class="cards compact" id="s-cards"></div>
<div class="row between"><p class="note">Switch a card on or off, set its identities. Only that card's worker restarts; the node keeps running.</p><button class="btn small" id="s-cards-save">Apply</button></div>
</div>
<div class="field">
<label class="switch"><input type="checkbox" id="s-vote"><span class="track"></span><span>Vote on finality checkpoints</span></label>
<label class="switch"><input type="checkbox" id="s-prove"><span class="track"></span><span>Prove assigned shards (proving v0; on a Mac the CPU prover is slow)</span></label>
<label class="switch"><input type="checkbox" id="s-trust"><span class="track"></span><span>Trust proof records without verifying them (devnet only)</span></label>
<p class="note" id="s-trust-note">Only when no verifier is found next to the engine: the node then includes proof records it never checked. Never on a testnet. A found verifier always wins. Changing this restarts the node.</p>
<label class="switch"><input type="checkbox" id="s-login"><span class="track"></span><span>Start at login</span></label>
</div>
<div class="field">
<div class="k">power control</div>
<label class="switch"><input type="checkbox" id="s-power-control"><span class="track"></span><span>Cap the NVIDIA cards' power and run the efficiency sweep</span></label>
<p class="note">Windows asks for administrator rights once; the cap and the sweep need them. Off, the app never asks. <span id="s-power-note"></span></p>
</div>
<div class="field">
<div class="k">efficiency sweep</div>
<label class="switch"><input type="checkbox" id="s-sweep"><span class="track"></span><span>Find each NVIDIA card's best MH per watt (once after install, then weekly)</span></label>
<p class="note">On the live program, never restarting the worker: the cap steps from 100% of the card's default limit down to 50%, 15 s to settle and 60 s to measure per step, then holds the step with the most MH per watt. One administrator prompt per sweep. It stops at once if the card faults, a remote job takes the GPU, or the hour boundary is near. A cap you set with the slider is pinned: the sweep records, but leaves it. "Sweep now" on a card's tile runs one at any time; the table is in the log (SWEEP lines).</p>
</div>
<div class="field">
<div class="k">version</div>
<div class="row between"><span class="mono" id="s-version"></span><span class="row"><button class="btn small primary" id="s-install" hidden>Install now</button><button class="btn small" id="s-update">Check now</button></span></div>
<label class="switch"><input type="checkbox" id="s-auto-update"><span class="track"></span><span>Install updates by itself at a safe moment</span></label>
<p class="note" id="s-update-note"></p>
</div>
<div class="field">
<div class="k">remote jobs</div>
<div class="row between"><label class="switch"><input type="checkbox" id="s-jobs-allow"><span class="track"></span><span>Allow remote jobs from Igneum (signed)</span></label><button class="btn small" id="s-jobs-check">Check now</button></div>
<p class="note">Igneum publishes signed jobs (a benchmark, a script, a file to fetch, logs to collect, a restart) next to the update manifest. This machine runs each one once and reports to the Igneum log intake. Only jobs signed by the key below run; nothing else can send one.</p>
<p class="note mono small" id="s-jobs-key"></p>
<p class="note" id="s-jobs-note"></p>
<div class="job-history" id="s-jobs-history"></div>
</div>
<div class="field">
<div class="k">folders</div>
<p class="note mono small" id="s-node-dir"></p>
<p class="note mono small" id="s-log-dir"></p>
</div>
<div class="field">
<button class="btn small ghost" id="s-live" hidden>Open the live devnet page</button>
</div>
</div>
</aside>
</div>
<!-- the log drawer: chips filter by source, search filters by text, the ruler and the jump controls move in time.
The list is virtualised (fixed row height, only the visible rows are in the DOM) so 20,000 lines scroll smoothly. -->
<div class="drawer" id="drawer" aria-label="Logs">
<div class="drawer-grip" id="drawer-grip" title="Drag to resize"><i></i></div>
<div class="drawer-head">
<div class="chips" id="log-chips">
<button class="chip on" data-src="">all</button>
<button class="chip" data-src="app">app</button>
<button class="chip" data-src="node">node</button>
<button class="chip" data-src="miner">miner</button>
<button class="chip" data-src="watch">watch</button>
</div>
<div class="log-search">
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="11" cy="11" r="7"></circle><path d="m20 20-3.5-3.5"></path></svg>
<input type="search" id="log-search" placeholder="search" spellcheck="false" autocomplete="off" aria-label="Search the log">
<span class="count mono" id="log-count"></span>
</div>
<div class="log-nav">
<input type="text" class="mono" id="log-time" placeholder="HH:MM:SS" maxlength="8" spellcheck="false" autocomplete="off" aria-label="Jump to a time" title="Jump to a time (Enter)">
<button class="chip" id="log-last-error" title="Jump to the last error">last error</button>
<button class="chip" id="log-last-swap" title="Jump to the last program swap">last swap</button>
</div>
<div class="log-tools">
<span class="mono dim at" id="log-at" title="Time of the top line in view"></span>
<button class="chip" id="log-wrap" title="Wrap long lines (up to 5,000 lines)">wrap</button>
<button class="chip" id="log-copy" title="Copy the lines in view">copy</button>
<label class="check small"><input type="checkbox" id="log-follow" checked><span>follow</span></label>
</div>
</div>
<div class="log-ruler" id="log-ruler" title="Click to jump"><span class="t0 mono" id="log-ruler-0"></span><span class="t1 mono" id="log-ruler-1"></span><i class="win" id="log-ruler-win"></i></div>
<div class="log-view" id="log-view">
<div class="log-pad" id="log-pad"><pre class="log" id="log"></pre></div>
<div class="log-empty mono" id="log-empty" hidden>Nothing logged yet.</div>
</div>
<button class="btn small log-jump" id="log-jump" hidden><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5v14M5 12l7 7 7-7"></path></svg><span id="log-jump-text">Newest</span></button>
</div>
<footer class="bottom" id="bottom" hidden>
<div class="left">
<button class="btn small" id="btn-pause">Pause</button>
<button class="btn small ghost" id="btn-logs" aria-expanded="false" aria-controls="drawer"><span id="btn-logs-text">Logs</span><svg class="chev" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 15 6-6 6 6"></path></svg></button>
</div>
<div class="mid mono" id="foot-status" aria-live="off"></div>
<div class="right">
<span class="mono dim" id="foot-version"></span>
<button class="btn small ghost danger" id="btn-quit">Quit</button>
</div>
</footer>
<div class="toast" id="toast" hidden></div>
<script src="app.js"></script>
</body>
</html>