Put the join inside the wizard, send a partner to Partnership when it finishes, and say what is left
This commit is contained in:
@@ -192,6 +192,160 @@ function vvPrompt(text, def, opts) {
|
||||
}, function (val) { resolve(val === false ? null : String(val)); });
|
||||
});
|
||||
}
|
||||
|
||||
// ── Mirror onboarding, shared by the Partnership tab and the first-run wizard ─────────────────
|
||||
//
|
||||
// Here for the reason stated above: only one pages/*.php is included per request, and both the
|
||||
// wizard and the Partnership tab need to render the same panel and run the same job. A second
|
||||
// copy in setup.php would drift from the one in partnership.php, and the panel encodes a detail
|
||||
// that is easy to get wrong on a copy — the terminal command must carry the SERVING host's
|
||||
// SCRIPTS_DIR, because it is pasted into a terminal on that machine.
|
||||
//
|
||||
// Not in js/varaverk.js: that loads below the tab include, and the wizard returns before it.
|
||||
|
||||
// api/run.php answers when a job is LAUNCHED, not finished. An empty body is never success —
|
||||
// Unraid's CSRF guard exits with one, and so does a PHP fatal.
|
||||
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 => {
|
||||
if (!text.trim()) throw new Error('Empty response — request rejected before it reached run.php');
|
||||
return JSON.parse(text);
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Polls the job's stat for liveness and its log for the step banner, so a launched job shows
|
||||
// where it is instead of appearing to do nothing for minutes.
|
||||
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');
|
||||
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>`;
|
||||
// Only the Partnership tab defines a reload; the wizard has nothing to refresh.
|
||||
if (typeof _vvPtReload === 'function') _vvPtReload();
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
tick();
|
||||
_vvJobPoll[id] = setInterval(tick, 4000);
|
||||
}
|
||||
|
||||
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 it runs. A timed re-enable invited the second click whose lock refusal
|
||||
// overwrote the live run's job record.
|
||||
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';
|
||||
});
|
||||
}
|
||||
|
||||
// The mirror's two-step join. Step 1 is a terminal step on purpose: ssh_setup.sh runs ssh-copy-id,
|
||||
// which prompts for the owner's root password on a first install, and a WebGUI button cannot
|
||||
// answer a password prompt. Offering only a button here was offering the one route that cannot
|
||||
// work — it failed on ssh-copy-id every time.
|
||||
// opts: {ownerName, termBase, termCmd, phase, hasPartner}
|
||||
function vvRenderMirrorOnboard(opts) {
|
||||
const owner = vvEscHtml(opts.ownerName || 'the owner');
|
||||
const dis = opts.hasPartner === false ? 'disabled style="opacity:.35;cursor:default;"' : '';
|
||||
|
||||
if ((opts.phase ?? 0) >= 1) {
|
||||
return `<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 ${owner} to run Phase 2</div>
|
||||
</div>
|
||||
<button class="vv-pt-action-btn run" onclick="vvPtOnboard(this)" ${dis}
|
||||
style="font-size:11px;white-space:nowrap;">▶ Onboard</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
return `<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 ${owner}</span>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:4px;">
|
||||
<a href="${opts.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('${opts.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">${opts.termCmd}</code>
|
||||
</div>
|
||||
<div style="font-size:9px;color:#2a2a2a;">Enter ${owner} 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 ${owner} to run Phase 2 · if key already installed</div>
|
||||
</div>
|
||||
<button class="vv-pt-action-btn run" onclick="vvPtOnboard(this)" ${dis}
|
||||
style="font-size:11px;white-space:nowrap;">▶ Onboard</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php
|
||||
|
||||
Reference in New Issue
Block a user