Redesign Cancel/Delete Keys in Partnership Actions

onboard_cancel.sh:
- --direction=h1 (default): remove HOST1's key from HOST2 + delete local pair
- --direction=h2: remove HOST2's key from HOST1's authorized_keys (by hostname match)
- --direction=both: both directions (used by Cancel button at phase 1)

pages/partnership.php:
- Cancel button: phase 1 only, runs --direction=both (full undo)
- Delete Keys (🗑): available at all phases, expands inline panel showing
  HOST1→HOST2 and HOST2→HOST1 as separate removal buttons with descriptions
- _vvDeleteKeys toggle state persists across polls alongside _vvOnboarding
- vvPtShowDeleteKeys / vvPtHideDeleteKeys top-level toggle fns
- vvPtDeleteH1 / vvPtDeleteH2 targeted removal fns
This commit is contained in:
Gmer4Lfe
2026-05-30 21:29:20 -04:00
parent 1956b6b068
commit 2989cefb79
2 changed files with 180 additions and 126 deletions
+82 -39
View File
@@ -56,9 +56,10 @@
</div>
<script>
// ── Onboard toggle state — persists across 10s poll re-renders ─────────────────
const _vvOnboarding = {}; // hostId → true when steps are expanded
let _vvPtReload = null; // set by IIFE so top-level fns can trigger a refresh
// ── Toggle states — persist across 10s poll re-renders ────────────────────────
const _vvOnboarding = {}; // hostId → true when onboard steps are expanded
const _vvDeleteKeys = {}; // hostId → true when delete-keys panel is expanded
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) {
@@ -78,12 +79,22 @@ function _vvPtRun(id, extraArgs) {
function vvPtStartOnboard(hostId) {
_vvOnboarding[hostId] = true;
delete _vvDeleteKeys[hostId];
if (_vvPtReload) _vvPtReload();
}
function vvPtEndOnboard(hostId) {
delete _vvOnboarding[hostId];
if (_vvPtReload) _vvPtReload();
}
function vvPtShowDeleteKeys(hostId) {
_vvDeleteKeys[hostId] = true;
delete _vvOnboarding[hostId];
if (_vvPtReload) _vvPtReload();
}
function vvPtHideDeleteKeys(hostId) {
delete _vvDeleteKeys[hostId];
if (_vvPtReload) _vvPtReload();
}
function vvPtPhase2(btn, hostId) {
if (!confirm(`Phase 2: Deploy containers + arr stack + establish partnership on ${hostId}?\n\nRequires ${hostId} to have Varaverk installed and SSH keys set up.`)) return;
@@ -126,15 +137,33 @@ function vvPtLocalSetup(btn) {
}
function vvPtCancel(btn, hostId) {
if (!confirm(`Cancel Phase 1 for ${hostId}?\n\nThis will:\n• Remove HOST1's SSH key from ${hostId}'s authorized_keys\n• Delete the local key pair\n• Reset phase state on both hosts\n\nContinue?`)) return;
if (!confirm(`Cancel onboard for ${hostId}?\n\nThis will remove keys in both directions and reset all phase state.\n\nContinue?`)) return;
btn.disabled = true;
btn.textContent = '⟳ Cancelling…';
_vvPtRun('Partnership/onboard_cancel.sh')
_vvPtRun('Partnership/onboard_cancel.sh', '--direction=both')
.then(d => { if (!d.ok) alert('Failed: ' + (d.error ?? 'Unknown error')); })
.catch(e => alert('Error: ' + e))
.finally(() => setTimeout(() => { btn.disabled = false; btn.textContent = '✕ Cancel'; }, 4000));
}
function vvPtDeleteH1(btn, hostId) {
if (!confirm(`Remove HOST1's key from ${hostId}?\n\n• Deletes local SSH key pair\n• Removes it from ${hostId}'s authorized_keys\n• Clears phase state`)) return;
btn.disabled = true; btn.textContent = '⟳ Removing…';
_vvPtRun('Partnership/onboard_cancel.sh', '--direction=h1')
.then(d => { if (!d.ok) alert('Failed: ' + (d.error ?? 'Unknown error')); })
.catch(e => alert('Error: ' + e))
.finally(() => setTimeout(() => { btn.disabled = false; btn.textContent = '✕ Remove HOST1 key'; }, 4000));
}
function vvPtDeleteH2(btn, hostId) {
if (!confirm(`Remove ${hostId}'s key from HOST1?\n\n• Removes ${hostId}'s public key from HOST1's authorized_keys\n• ${hostId} will no longer be able to SSH into HOST1`)) return;
btn.disabled = true; btn.textContent = '⟳ Removing…';
_vvPtRun('Partnership/onboard_cancel.sh', '--direction=h2')
.then(d => { if (!d.ok) alert('Failed: ' + (d.error ?? 'Unknown error')); })
.catch(e => alert('Error: ' + e))
.finally(() => setTimeout(() => { btn.disabled = false; btn.textContent = `✕ Remove ${hostId} key`; }, 4000));
}
function vvPtOffboard(btn) {
if (btn.style.opacity === '0.35' || btn.style.cursor === 'default') return;
if (!confirm('Run partnership_offboard.sh?\n\nThis will end the partnership, reconfigure WebUIs, and revoke SSH access.\n\nContinue?')) return;
@@ -345,78 +374,63 @@ function _renderActions(nodes, cfg) {
<span style="font-size:10px;color:${dotCol};">● ${dotLbl}</span>
</div>`;
const isDeleting = !!_vvDeleteKeys[remote.id];
// ── Phase 0: not provisioned ─────────────────────────────────────────
if (phase === 0) {
if (isOnboarding) {
// Expanded step-by-step onboard card
html += `<div style="background:#0d0d0d;border:1px solid #1e1e1e;border-radius:4px;padding:10px 12px;">
<div style="display:flex;align-items:baseline;gap:8px;margin-bottom:8px;">
<span style="font-size:9px;color:#4a9eff;background:#0e1a2a;padding:2px 8px;border-radius:10px;font-weight:600;">Step 1</span>
<span style="font-size:11px;color:#888;">Install SSH key on ${remote.hostname}</span>
</div>
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:4px;">
${termBase ? `<a href="${termBase}" target="_blank"
<a href="${termBase}" target="_blank"
style="padding:3px 10px;background:#1a2a3a;color:#7ab;border:1px solid #2e4a6b;
border-radius:3px;text-decoration:none;font-size:10px;white-space:nowrap;">🖥 Open Terminal</a>` : ''}
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='#555',1500)})"
style="font-size:9px;color:#555;background:#0a0a0a;padding:3px 8px;border-radius:3px;
border:1px solid #1a1a1a;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;margin-bottom:14px;">Enter ${remote.hostname} root password when prompted · this tab auto-updates on completion</div>
<div style="font-size:9px;color:#2a2a2a;margin-bottom:14px;">Enter ${remote.hostname} root password when prompted · tab auto-updates on completion</div>
<div style="display:flex;align-items:baseline;gap:8px;margin-bottom:8px;">
<span style="font-size:9px;color:#4caf50;background:#0a1a0a;padding:2px 8px;border-radius:10px;font-weight:600;">Step 2</span>
<span style="font-size:11px;color:#888;">Push conf</span>
<span style="font-size:9px;color:#2a2a2a;">if key already installed separately</span>
</div>
<button class="vv-pt-action-btn info" onclick="vvPtPushConf(this,'${remote.id}')"
title="Runs --phase1-only --skip-ssh — use when SSH key is already on ${remote.hostname}">
▶ Push Conf
</button>
title="Runs --phase1-only --skip-ssh">▶ Push Conf</button>
</div>
<div class="vv-pt-actions" style="margin-top:8px;">
<button class="vv-pt-action-btn warn" onclick="vvPtEndOnboard('${remote.id}')" style="opacity:.7;">
✕ Close
</button>
<button class="vv-pt-action-btn warn" onclick="vvPtCancel(this,'${remote.id}')" style="opacity:.6;">
✕ Reset keys
</button>
<button class="vv-pt-action-btn warn" onclick="vvPtEndOnboard('${remote.id}')" style="opacity:.6;">✕ Close</button>
</div>`;
} else {
// Compact row
html += `<div style="display:flex;align-items:center;gap:10px;">
html += `<div style="display:flex;align-items:center;gap:10px;flex-wrap:wrap;">
<span style="font-size:10px;color:#444;">○ Not provisioned</span>
<button class="vv-pt-action-btn run" onclick="vvPtStartOnboard('${remote.id}')"
title="Show initialization steps for ${remote.hostname}">
▶ Onboard
</button>
<button class="vv-pt-action-btn warn" onclick="vvPtCancel(this,'${remote.id}')"
style="opacity:.5;font-size:11px;" title="Remove any partial keys and reset state">
✕ Reset
</button>
<button class="vv-pt-action-btn run" onclick="vvPtStartOnboard('${remote.id}')">▶ Onboard</button>
<button class="vv-pt-action-btn warn" onclick="vvPtShowDeleteKeys('${remote.id}')"
style="opacity:.5;font-size:11px;">🗑 Delete Keys</button>
</div>`;
}
// ── Phase 1: SSH done, awaiting HOST2 install ──────────────────────────
// ── Phase 1: SSH done, awaiting HOST2 ─────────────────────────────────
} else if (phase === 1) {
html += `<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:6px;">
<span style="font-size:10px;color:#ff9800;background:#1a1200;padding:2px 8px;border-radius:10px;border:1px solid #3a2800;">⏳ SSH ready</span>
${remote.ts_ip ? `<span style="font-size:9px;color:#2a2a2a;">${remote.ts_ip}</span>` : ''}
</div>
<div style="font-size:10px;color:#444;margin-bottom:8px;">
Waiting for ${remote.id} to install Varaverk and complete its onboard.
Phase 2 triggers automatically when it does.
Waiting for ${remote.id} to install Varaverk and complete its onboard. Phase 2 triggers automatically.
</div>
<div class="vv-pt-actions">
<button class="vv-pt-action-btn info" onclick="vvPtPhase2(this,'${remote.id}')"
title="Manually trigger Phase 2 if ${remote.id} auto-notification did not arrive">
▶ Run Phase 2 Manually
</button>
<button class="vv-pt-action-btn warn" onclick="vvPtCancel(this,'${remote.id}')"
style="opacity:.7;">
✕ Cancel
</button>
<button class="vv-pt-action-btn warn" onclick="vvPtCancel(this,'${remote.id}')">✕ Cancel</button>
<button class="vv-pt-action-btn warn" onclick="vvPtShowDeleteKeys('${remote.id}')"
style="opacity:.6;font-size:11px;">🗑 Delete Keys</button>
</div>`;
// ── Phase 2: fully onboarded ───────────────────────────────────────────
@@ -434,9 +448,38 @@ function _renderActions(nodes, cfg) {
</div>
<div class="vv-pt-actions">
<button class="vv-pt-action-btn info" onclick="vvPtPhase2(this,'${remote.id}')"
style="opacity:.4;" title="Re-run Phase 2 redeploy containers and re-establish partnership">
↻ Re-run Phase 2
</button>
style="opacity:.4;" title="Re-run Phase 2">↻ Re-run Phase 2</button>
<button class="vv-pt-action-btn warn" onclick="vvPtShowDeleteKeys('${remote.id}')"
style="opacity:.5;font-size:11px;">🗑 Delete Keys</button>
</div>`;
}
// ── Delete Keys panel (any phase) ──────────────────────────────────────
if (isDeleting) {
html += `<div style="margin-top:10px;background:#0d0d0d;border:1px solid #2a1a1a;border-radius:4px;padding:10px 12px;">
<div style="font-size:10px;color:#888;margin-bottom:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;">Remove SSH access</div>
<div style="display:flex;flex-direction:column;gap:8px;">
<div style="display:flex;align-items:center;justify-content:space-between;gap:8px;">
<div>
<div style="font-size:11px;color:#bbb;">HOST1 ${remote.id}</div>
<div style="font-size:9px;color:#333;">HOST1's key on ${remote.hostname} · deletes local key pair</div>
</div>
<button class="vv-pt-action-btn warn" onclick="vvPtDeleteH1(this,'${remote.id}')"
style="white-space:nowrap;font-size:11px;">✕ Remove HOST1 key</button>
</div>
<div style="display:flex;align-items:center;justify-content:space-between;gap:8px;">
<div>
<div style="font-size:11px;color:#bbb;">${remote.id} → HOST1</div>
<div style="font-size:9px;color:#333;">${remote.hostname}'s key on HOST1 · removes from authorized_keys</div>
</div>
<button class="vv-pt-action-btn warn" onclick="vvPtDeleteH2(this,'${remote.id}')"
style="white-space:nowrap;font-size:11px;"> Remove ${remote.id} key</button>
</div>
</div>
<div class="vv-pt-actions" style="margin-top:10px;">
<button class="vv-pt-action-btn info" onclick="vvPtHideDeleteKeys('${remote.id}')"
style="font-size:11px;opacity:.7;"> Done</button>
</div>
</div>`;
}