diff --git a/build/dashboard/mining_dashboard/web/static/wizard.mjs b/build/dashboard/mining_dashboard/web/static/wizard.mjs index fc349f9b..ecea3722 100644 --- a/build/dashboard/mining_dashboard/web/static/wizard.mjs +++ b/build/dashboard/mining_dashboard/web/static/wizard.mjs @@ -87,7 +87,16 @@ export const Gate = ({ error, onSubmit }) => html`
This is what the machine will be.
+ <${Field} label="Worker name">${handoff.worker}/>
+ <${Field} label="Mines toward">${handoff.stratum}/>
+ <${Note}>A rig has no dashboard and no login — nothing to save. It appears by this
+ name in the Pithead's Workers view./>
+ `
+ : handoff
+ ? html`This is shown once, here.
<${Field} label="Dashboard user">${handoff.username}/>
<${Field} label="Dashboard password">${handoff.password}/>
@@ -177,7 +207,12 @@ export const Done = ({ status, handoff, installer, onAck }) => html`Provisioning. The machine is pulling and starting the stack — 10 to 30 minutes on a home connection. This page will stop responding while it happens; that is the machine working, not failing. Its console narrates, and when it finishes the dashboard is at @@ -201,6 +236,14 @@ export class WizardApp extends Component { jsonText: "", jsonError: "", authMode: "auto", // auto | set | none — travels beside the config (see wizard.py submit) + // What this machine IS — the first disclosure, above the disk. pithead | both | rig. + // "both" rides the existing local_miner switch; "rig" collapses the form to three answers + // that travel beside the config exactly like authMode does. + role: "pithead", + rigPool: "", + rigWorker: "", + rigPassword: "", + rigDefaults: {}, status: "", handoff: null, }; @@ -222,8 +265,15 @@ export class WizardApp extends Component { reference: s.reference, disks: s.disks, error: s.error || "", + rigDefaults: s.rig_defaults || {}, handoff: s.handoff || null, }; + // The host's discovery pre-fills the rig fields, but only while they are untouched — the + // form polls, and a half-typed pool address must survive it (same rule as cfg below). + if (!this.state.rigPool && !this.state.rigWorker) { + next.rigPool = (s.rig_defaults || {}).pool || ""; + next.rigWorker = (s.rig_defaults || {}).worker || ""; + } // The wait is over once the server either moved on or rejected: both end "Validating…". if (this.state.submitting && (next.stage !== "setup" || next.error)) next.submitting = false; // Do not clobber in-progress editing with the server's copy once the form is up. @@ -272,6 +322,26 @@ export class WizardApp extends Component { this.setState({ cfg, jsonText: JSON.stringify(cfg, null, 2) }); }; + // The role reshapes the page the way the disk choice does. "Both" IS the existing + // local_miner switch — the role presets it and the switch below stays live; back to plain + // Pithead resets it to the documented default so the submitted config is byte-for-byte + // today's. The rig role never touches the config at all. + setRole = (e) => { + const role = e.target.value; + const cfg = this.state.cfg; + const next = { role, cfg }; + if (role !== "rig") { + pathSet(cfg, "local_miner.enabled", role === "both"); + // "usb" only exists for rigs — a coordinator switching back must re-pick a real disk. + if (this.state.chosen === "usb") { + next.chosen = ""; + next.confirm = ""; + } + } + next.jsonText = JSON.stringify(cfg, null, 2); + this.setState(next); + }; + // JSON pane edit → config → fields. Hand-edited JSON wins; shape errors show as typed. editJson = (e) => { const text = e.target.value; @@ -285,31 +355,53 @@ export class WizardApp extends Component { submit = async (e) => { e.preventDefault(); + const rig = this.state.role === "rig"; const keepEverything = this.state.installer && (this.state.disks.find((d) => d.name === this.state.chosen) || {}).state === "pithead-with-data" && this.state.wipe === "keep"; + // keep means KEEP in every role: no config, no role — the survivor wins. const body = keepEverything ? {} // the preserved config wins — sending one would only mislead - : { - config: JSON.stringify(this.state.cfg), - auth_mode: this.state.authMode, - }; + : rig + ? { + role: "rig", + rig_pool: this.state.rigPool.trim(), + rig_worker: this.state.rigWorker.trim(), + rig_password: this.state.rigPassword, + } + : { + config: JSON.stringify(this.state.cfg), + auth_mode: this.state.authMode, + }; + if (rig && !keepEverything && !body.rig_pool) { + this.setState({ error: "Enter the pool address (host:port)." }); + return; + } // One page, one submission: on the installation medium the disk choice rides beside the - // config, and the server gates both before anything is written. + // config, and the server gates both before anything is written. "usb" (rig role only) is + // not a disk: nothing is erased, so the retyped-name gate does not apply. if (this.state.installer) { if (!this.state.chosen) { - this.setState({ error: "Choose the disk to install onto." }); + this.setState({ + error: rig + ? "Choose a disk — or run from this USB stick." + : "Choose the disk to install onto.", + }); return; } - if (this.state.confirm !== this.state.chosen) { - this.setState({ error: `Type ${this.state.chosen} exactly to confirm the erase.` }); - return; + if (this.state.chosen === "usb") { + body.disk = "usb"; + } else { + if (this.state.confirm !== this.state.chosen) { + this.setState({ error: `Type ${this.state.chosen} exactly to confirm the erase.` }); + return; + } + body.disk = this.state.chosen; + body.confirm = this.state.confirm; + body.wipe = this.state.wipe; } - body.disk = this.state.chosen; - body.confirm = this.state.confirm; - body.wipe = this.state.wipe; } const res = await fetch("/submit", { method: "POST", body: new URLSearchParams(body) }); if (!res.ok) { @@ -332,6 +424,39 @@ export class WizardApp extends Component { await this.loadState(); // the server drops out of the handoff stage; the view follows }; + // The rig role's whole form: where the pool is, what to call the machine, an optional + // stratum password. No payout addresses (the Pithead holds those), no nodes, no dashboard + // login — a rig has none of them. + renderRigFields() { + const { rigPool, rigWorker, rigPassword, rigDefaults } = this.state; + return html`
${rigDefaults.pool}${" "}on this
+ network — already filled in.`
+ : html`No Pithead answered on this network — enter the coordinator's address by
+ hand. Its own setup card names it, as ${" "}
+ stratum+tcp://…${" "}under "Point miners at".`
+ }/>
+ <${Field} label="Worker name">
+ this.setState({ rigWorker: e.target.value })}
+ autocomplete="off" autocapitalize="off" spellcheck=${false} />
+ />
+ <${Field} label="Stratum password (leave blank unless the Pithead set one)">
+ this.setState({ rigPassword: e.target.value })}
+ autocomplete="new-password" />
+ />
+ <${Note}>That is everything a rig needs. It has no dashboard and no login of its own —
+ it appears by this name in the Pithead's Workers view./>`;
+ }
+
renderSetup() {
const { cfg, error, jsonText, jsonError } = this.state;
const v = (name) => pathGet(cfg, FIELDS[name].path);
@@ -341,44 +466,64 @@ export class WizardApp extends Component {
const remoteMonero = v("moneroMode") === "remote";
const remoteTari = v("tariMode") === "remote";
const { installer, disks, chosen, confirm, wipe } = this.state;
+ const rig = this.state.role === "rig";
// Keep-everything reinstall: the machine's settings, wallets, login and chains all survive,
// so there is nothing to ask — the config half of the page would collect answers the
// machine will ignore (its preserved config wins). Only the disk half renders.
const pickedState = (disks.find((d) => d.name === chosen) || {}).state;
// Progressive disclosure: the DISK decides what gets asked (keep = nothing, keep-chains =
// a trimmed form, fresh = everything), so until one is chosen the page asks only that.
- const diskPicked = !installer || Boolean(pickedState);
+ // The rig role's "usb" target counts as picked: running from the stick is a full answer.
+ const diskPicked = !installer || Boolean(pickedState) || chosen === "usb";
const keepEverything = installer && pickedState === "pithead-with-data" && wipe === "keep";
return html`${ installer && !diskPicked - ? html`Choose the disk to install onto — what happens next depends on what is + ? rig + ? html`Choose where this rig runs — one of the disks, or straight from this USB + stick. The rest of the page appears once you pick.` + : html`Choose the disk to install onto — what happens next depends on what is already on it, so the rest of the page appears once you pick.` : keepEverything ? html`This disk keeps everything — settings, wallets, dashboard login and the synced chains. Only the system is replaced, so there is nothing to configure: the machine comes back exactly as it was, on a fresh install.` - : installer - ? html`Choose the disk to install onto and answer the questions below — the + : rig + ? html`A rig needs almost nothing: where the pool is, and what to call this + machine. It mines toward a Pithead and appears in that machine's dashboard.` + : installer + ? html`Choose the disk to install onto and answer the questions below — the machine validates everything, shows you the login to save, and only then erases the disk. After it switches itself off, remove the stick and power it on: it provisions itself with exactly this configuration.` - : html`Only the answers that cannot be guessed for you. Everything else keeps its - documented default and stays editable from the dashboard.` + : html`Only the answers that cannot be guessed for you. Everything else keeps + its documented default and stays editable from the dashboard.` }
<${Err}>${error}/>