Put the join inside the wizard, send a partner to Partnership when it finishes, and say what is left
This commit is contained in:
@@ -172,26 +172,6 @@ const _vvDeleteKeys = {}; // hostId → true when delete-keys panel is expande
|
||||
let _vvPtReload = null; // set by IIFE so top-level fns can trigger a refresh
|
||||
|
||||
// ── Public action functions — top-level so onclick= attributes can reach them ──
|
||||
function _vvPtRun(id, extraArgs) {
|
||||
const params = {id, manual: '1'};
|
||||
if (extraArgs) params.extra_args = extraArgs;
|
||||
return fetch('/plugins/varaverk/api/run.php', {
|
||||
method: 'POST',
|
||||
body: new URLSearchParams(params)
|
||||
}).then(r => {
|
||||
if (!r.ok) throw new Error('HTTP ' + r.status);
|
||||
return r.text();
|
||||
}).then(text => {
|
||||
// An empty body is never success. api/run.php echoes JSON on every path it can reach, so
|
||||
// nothing arriving means the request died before it: Unraid's CSRF guard exits with an empty
|
||||
// body on a token mismatch, nginx returns an empty 200 for a bodyless POST, and a PHP fatal
|
||||
// produces the same. Returning {ok:true} for that made a killed request and a launched job
|
||||
// indistinguishable — press Onboard, get no error, and nothing has run. vvApiKey below has
|
||||
// always thrown on this; the action path is what disagreed.
|
||||
if (!text.trim()) throw new Error('Empty response — request rejected before it reached run.php');
|
||||
return JSON.parse(text);
|
||||
});
|
||||
}
|
||||
|
||||
// ── Live progress for a launched job ──────────────────────────────────────────
|
||||
// api/run.php returns {ok:true} the instant the job is *launched*, not when it finishes, and
|
||||
@@ -201,46 +181,7 @@ function _vvPtRun(id, extraArgs) {
|
||||
//
|
||||
// Polls the job's stat for liveness and its log for the step banner run_job.sh is capturing, and
|
||||
// renders the last "━━━ Step … ━━━" line as the current activity.
|
||||
const _vvJobPoll = {};
|
||||
|
||||
function vvPtWatchJob(id, mountEl) {
|
||||
if (_vvJobPoll[id]) clearInterval(_vvJobPoll[id]);
|
||||
const enc = encodeURIComponent(id);
|
||||
|
||||
const tick = () => {
|
||||
fetch(`/plugins/varaverk/api/status.php?id=${enc}&_=${Date.now()}`)
|
||||
.then(r => r.json())
|
||||
.then(s => {
|
||||
if (!s.ok) return;
|
||||
if (s.status === 'running') {
|
||||
return fetch(`/plugins/varaverk/api/log.php?id=${enc}&_=${Date.now()}`)
|
||||
.then(r => r.json())
|
||||
.then(l => {
|
||||
const lines = (l.content || '').split('\n');
|
||||
// Last step banner wins — that is where the run currently is.
|
||||
let step = '';
|
||||
for (let i = lines.length - 1; i >= 0; i--) {
|
||||
const m = lines[i].match(/━━━\s*(?:[^\s]+\s+)?(Step [^━]+?)\s*━━━/);
|
||||
if (m) { step = m[1].trim(); break; }
|
||||
}
|
||||
mountEl.innerHTML = `<span style="color:#4a9eff;">⟳ running</span>`
|
||||
+ (step ? ` <span style="color:#666;">· ${vvEscHtml(step)}</span>` : '');
|
||||
});
|
||||
}
|
||||
clearInterval(_vvJobPoll[id]);
|
||||
delete _vvJobPoll[id];
|
||||
const col = s.status === 'ok' ? '#4caf50' : (s.status === 'warn' ? '#ff9800' : '#f44336');
|
||||
const lbl = s.status === 'ok' ? 'complete ✅'
|
||||
: (s.status === 'never_run' ? 'did not start ⚠' : `${s.status} (exit ${s.exit ?? '?'})`);
|
||||
mountEl.innerHTML = `<span style="color:${col};">${lbl}</span>`
|
||||
+ ` <a href="?tab=scheduler" class="localURL" style="color:#556;margin-left:6px;">log</a>`;
|
||||
if (_vvPtReload) _vvPtReload();
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
tick();
|
||||
_vvJobPoll[id] = setInterval(tick, 4000);
|
||||
}
|
||||
|
||||
function vvPtStartOnboard(hostId) {
|
||||
_vvOnboarding[hostId] = true;
|
||||
@@ -298,38 +239,8 @@ async function vvPtPhase2(btn, hostId) {
|
||||
.finally(() => setTimeout(() => { btn.disabled = false; btn.textContent = '▶ Run Phase 2 Manually'; }, 3000));
|
||||
}
|
||||
|
||||
async function vvPtOnboard(btn) {
|
||||
if (!await vvConfirm('Run full partnership_onboard.sh?\n\nRun on the MIRROR first, then on the OWNER.\n\nUse Phase 1 + Phase 2 buttons for step-by-step control.')) return;
|
||||
btn.disabled = true;
|
||||
btn.textContent = '⟳ Starting…';
|
||||
// Stays disabled while the job runs. Re-enabling after a fixed timeout invited the second
|
||||
// click that overwrites the live run's job record with its own lock refusal.
|
||||
const prog = _vvJobProgressEl(btn);
|
||||
_vvPtRun('Partnership/partnership_onboard.sh')
|
||||
.then(d => {
|
||||
if (!d.ok) throw new Error(d.error ?? 'Unknown error');
|
||||
btn.textContent = '⟳ Running…';
|
||||
vvPtWatchJob('Partnership/partnership_onboard.sh', prog);
|
||||
})
|
||||
.catch(e => {
|
||||
prog.innerHTML = `<span style="color:#f44336;">failed to start — ${vvEscHtml(String(e.message || e))}</span>`;
|
||||
btn.disabled = false;
|
||||
btn.textContent = '▶ Onboard';
|
||||
});
|
||||
}
|
||||
|
||||
// One status line per button, inserted after it and reused across polls.
|
||||
function _vvJobProgressEl(btn) {
|
||||
let el = btn.parentElement.querySelector('.vv-jobprog');
|
||||
if (!el) {
|
||||
el = document.createElement('div');
|
||||
el.className = 'vv-jobprog';
|
||||
el.style.cssText = 'font-size:10px;margin-top:5px;white-space:nowrap;';
|
||||
btn.parentElement.appendChild(el);
|
||||
}
|
||||
el.innerHTML = '<span style="color:#4a9eff;">⟳ starting…</span>';
|
||||
return el;
|
||||
}
|
||||
|
||||
async function vvPtPushConf(btn, hostId) {
|
||||
if (!await vvConfirm(`Push conf to ${hostId}?\n\nAssumes SSH key is already installed on ${hostId}.\nSkips key generation/install, goes straight to conf push + local setup.`)) return;
|
||||
@@ -1247,70 +1158,18 @@ function _renderActions(nodes, cfg) {
|
||||
}
|
||||
|
||||
// ── Mirror onboard (non-owner) ─────────────────────────────────────────────
|
||||
//
|
||||
// The mirror's half of phase 1 is ssh_setup.sh doing ssh-copy-id *to the owner*, which
|
||||
// prompts for the owner's root password on a first install. A WebGUI button cannot answer a
|
||||
// password prompt, so until the key exists the only honest control here is the same
|
||||
// terminal + copy-command pair the owner's phase 0 panel has always had. The bare button
|
||||
// was the sole affordance on this side, which meant the mirror was offered the one route
|
||||
// that structurally cannot work — it failed on ssh-copy-id every time.
|
||||
//
|
||||
// Once the key is in, the button is exactly right, and it becomes the whole panel.
|
||||
// Rendered by vvRenderMirrorOnboard() in Varaverk.page so the wizard shows the identical
|
||||
// panel. Only one pages/*.php loads per request, so a copy here could not be reached from
|
||||
// setup.php — and two copies of a panel this fiddly would drift.
|
||||
if (!isOwner) {
|
||||
const ownerNode = remotes.find(n => n.is_owner) || remotes[0];
|
||||
const ownerName = ownerNode?.hostname || 'the owner';
|
||||
const myPhase = selfNode?.onboard_phase ?? 0;
|
||||
|
||||
if (myPhase >= 1) {
|
||||
html += `<div style="padding:10px 12px;background:#0d0d0d;border:1px solid #1e1e1e;border-radius:4px;">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:10px;">
|
||||
<div>
|
||||
<div style="font-size:11px;color:#888;margin-bottom:2px;">Join partnership</div>
|
||||
<div style="font-size:9px;color:#333;">SSH key ready · notifies ${vvEscHtml(ownerName)} to run Phase 2</div>
|
||||
</div>
|
||||
<button class="vv-pt-action-btn run" onclick="vvPtOnboard(this)"
|
||||
${!hasPartner ? 'disabled style="opacity:.35;cursor:default;"' : ''}
|
||||
style="font-size:11px;white-space:nowrap;">▶ Onboard</button>
|
||||
</div>
|
||||
</div>`;
|
||||
} else {
|
||||
html += `<div style="padding:10px 12px;background:#0d0d0d;border:1px solid #1e3a5a;border-radius:4px;">
|
||||
<div style="display:flex;flex-direction:column;gap:10px;">
|
||||
<div>
|
||||
<div style="display:flex;align-items:center;gap:6px;margin-bottom:6px;">
|
||||
<span style="font-size:9px;font-weight:700;color:#4a9eff;background:#0a1828;
|
||||
padding:2px 8px;border-radius:10px;border:1px solid #1a3a5a;">Step 1</span>
|
||||
<span style="font-size:11px;color:#888;">Install SSH key on ${vvEscHtml(ownerName)}</span>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:4px;">
|
||||
<a href="${termBase}" target="_blank" class="localURL"
|
||||
style="padding:3px 10px;background:#0e1a2a;color:#7ab;border:1px solid #1e3a5a;
|
||||
border-radius:3px;text-decoration:none;font-size:10px;white-space:nowrap;">Open Terminal</a>
|
||||
<code onclick="navigator.clipboard.writeText('${termCmd}').then(()=>{this.style.color='#4caf50';setTimeout(()=>this.style.color='#444',1500)})"
|
||||
style="font-size:9px;color:#444;background:#080808;padding:3px 8px;border-radius:3px;
|
||||
border:1px solid #181818;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;
|
||||
white-space:nowrap;cursor:pointer;" title="Click to copy">${termCmd}</code>
|
||||
</div>
|
||||
<div style="font-size:9px;color:#2a2a2a;">Enter ${vvEscHtml(ownerName)} root password when prompted · a button cannot answer that prompt</div>
|
||||
</div>
|
||||
<div style="border-top:1px solid #1a1a1a;padding-top:10px;">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:10px;">
|
||||
<div>
|
||||
<div style="display:flex;align-items:center;gap:6px;margin-bottom:2px;">
|
||||
<span style="font-size:9px;font-weight:700;color:#4caf50;background:#0a1a0a;
|
||||
padding:2px 8px;border-radius:10px;border:1px solid #1a3a1a;">Step 2</span>
|
||||
<span style="font-size:11px;color:#888;">Join partnership</span>
|
||||
</div>
|
||||
<div style="font-size:9px;color:#333;">Notifies ${vvEscHtml(ownerName)} to run Phase 2 · if key already installed</div>
|
||||
</div>
|
||||
<button class="vv-pt-action-btn run" onclick="vvPtOnboard(this)"
|
||||
${!hasPartner ? 'disabled style="opacity:.35;cursor:default;"' : ''}
|
||||
style="font-size:11px;white-space:nowrap;">▶ Onboard</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
html += vvRenderMirrorOnboard({
|
||||
ownerName: ownerNode?.hostname,
|
||||
termBase: termBase,
|
||||
termCmd: termCmd,
|
||||
phase: selfNode?.onboard_phase ?? 0,
|
||||
hasPartner: hasPartner,
|
||||
});
|
||||
}
|
||||
|
||||
// ── Offboard + Transfer ────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user