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:
@@ -1,36 +1,48 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# ==============================================================================================
|
# ==============================================================================================
|
||||||
# ============================= Onboard Cancel =================================================
|
# ============================= Delete Keys ====================================================
|
||||||
# ==============================================================================================
|
# ==============================================================================================
|
||||||
#
|
#
|
||||||
# PURPOSE
|
# PURPOSE
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
# Undoes Phase 1 of the onboard process:
|
# Removes SSH keys between HOST1 and HOST2 in the specified direction.
|
||||||
# 1. Remove HOST1's public key from HOST2's authorized_keys (while key still works)
|
# Safe to run at any phase. Clears related setup.db flags.
|
||||||
# 2. Remove phase flags from HOST2's varaverk_setup.db
|
|
||||||
# 3. Delete local SSH key pair
|
|
||||||
# 4. Clear phase flags from local varaverk_setup.db
|
|
||||||
#
|
|
||||||
# Run on the OWNER. If HOST2 is unreachable the local side is still cleaned up.
|
|
||||||
#
|
#
|
||||||
# ==============================================================================================
|
# ==============================================================================================
|
||||||
# RUNTIME MODES
|
# RUNTIME MODES
|
||||||
# ==============================================================================================
|
# ==============================================================================================
|
||||||
#
|
#
|
||||||
# Partnership/onboard_cancel.sh
|
# Partnership/onboard_cancel.sh --direction=h1 (default)
|
||||||
# Cancel Phase 1 — remove keys and reset state
|
# HOST1 → HOST2: remove HOST1's public key from HOST2's authorized_keys,
|
||||||
|
# delete local HOST1 key pair, clear HOST2 phase flags from setup.db.
|
||||||
|
#
|
||||||
|
# Partnership/onboard_cancel.sh --direction=h2
|
||||||
|
# HOST2 → HOST1: remove HOST2's public key from HOST1's authorized_keys.
|
||||||
|
# Identifies the key by HOST2's hostname in the key comment.
|
||||||
|
#
|
||||||
|
# Partnership/onboard_cancel.sh --direction=both
|
||||||
|
# Both directions.
|
||||||
#
|
#
|
||||||
# Partnership/onboard_cancel.sh --dry-run
|
# Partnership/onboard_cancel.sh --dry-run
|
||||||
# Preview all steps without making changes
|
# Preview without making changes.
|
||||||
#
|
#
|
||||||
# ==============================================================================================
|
# ==============================================================================================
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
SCRIPTS_ROOT="$SCRIPT_DIR/.."
|
SCRIPTS_ROOT="$SCRIPT_DIR/.."
|
||||||
SSH_TIMEOUT=15
|
SSH_TIMEOUT=15
|
||||||
|
DIRECTION="h1"
|
||||||
|
FILTERED_ARGS=()
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--direction=*) DIRECTION="${arg#--direction=}" ;;
|
||||||
|
*) FILTERED_ARGS+=("$arg") ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
source "$SCRIPTS_ROOT/load_config.sh"
|
source "$SCRIPTS_ROOT/load_config.sh"
|
||||||
parse_args "$@"
|
parse_args "${FILTERED_ARGS[@]}"
|
||||||
|
|
||||||
[[ "$EUID" -ne 0 ]] && { error "Must be run as root"; exit 1; }
|
[[ "$EUID" -ne 0 ]] && { error "Must be run as root"; exit 1; }
|
||||||
|
|
||||||
@@ -43,99 +55,98 @@ MIRROR_ID=$( [[ "$OWNER_ID" == "HOST1" ]] && echo "HOST2" || echo "HOST1" )
|
|||||||
MIRROR="${!MIRROR_ID}"
|
MIRROR="${!MIRROR_ID}"
|
||||||
SSH_KEY_PUB="${SSH_KEY}.pub"
|
SSH_KEY_PUB="${SSH_KEY}.pub"
|
||||||
STATE_FILE="/boot/config/varaverk_setup.db"
|
STATE_FILE="/boot/config/varaverk_setup.db"
|
||||||
|
AUTH_KEYS="/root/.ssh/authorized_keys"
|
||||||
|
MIRROR_SHORT="${MIRROR%%.*}"
|
||||||
|
|
||||||
START=$(date +%s)
|
START=$(date +%s)
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "━━━ Cancel Phase 1 — $MY_ID → $MIRROR_ID ($MIRROR) — $(date '+%Y-%m-%d %H:%M:%S') ━━━"
|
echo "━━━ Delete Keys — direction:${DIRECTION} — $(date '+%Y-%m-%d %H:%M:%S') ━━━"
|
||||||
echo ""
|
|
||||||
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no changes will be made"
|
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no changes will be made"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
REMOTE_CLEANED=false
|
H1_DONE=false
|
||||||
LOCAL_KEY_GONE=false
|
H2_DONE=false
|
||||||
STATE_CLEARED=false
|
|
||||||
|
|
||||||
# ── Step 1: Remove HOST1's public key from HOST2 ──────────────────────────────
|
# ── HOST1 → HOST2: remove HOST1's key from HOST2 + delete local pair ──────────
|
||||||
echo "━━━ Step 1 — Remove Public Key from $MIRROR_ID ━━━"
|
if [[ "$DIRECTION" == "h1" || "$DIRECTION" == "both" ]]; then
|
||||||
|
echo "━━━ HOST1 → HOST2: Remove HOST1 key from $MIRROR ━━━"
|
||||||
|
|
||||||
if [[ ! -f "$SSH_KEY_PUB" ]]; then
|
if [[ ! -f "$SSH_KEY_PUB" ]]; then
|
||||||
log "No local public key at $SSH_KEY_PUB — nothing to remove from $MIRROR_ID"
|
log "No local public key — nothing to remove from $MIRROR"
|
||||||
REMOTE_CLEANED=true
|
H1_DONE=true
|
||||||
else
|
else
|
||||||
# Extract the key blob (middle field of pub key) — used as a unique identifier.
|
|
||||||
# Use | as sed delimiter to avoid clashing with base64 / characters in the blob.
|
|
||||||
KEY_BLOB=$(awk '{print $2}' "$SSH_KEY_PUB")
|
KEY_BLOB=$(awk '{print $2}' "$SSH_KEY_PUB")
|
||||||
MIRROR_IP=$(resolve_tailscale_ip "$MIRROR" 2>/dev/null || true)
|
MIRROR_IP=$(resolve_tailscale_ip "$MIRROR" 2>/dev/null || true)
|
||||||
|
|
||||||
if [[ -z "$MIRROR_IP" ]]; then
|
if [[ -z "$MIRROR_IP" ]]; then
|
||||||
warn "Cannot resolve $MIRROR Tailscale IP — skipping remote cleanup"
|
warn "Cannot resolve $MIRROR Tailscale IP — remove HOST1 key from $MIRROR manually"
|
||||||
warn "Remove HOST1's public key from $MIRROR:/root/.ssh/authorized_keys manually"
|
|
||||||
elif [[ "$DRY_RUN" == true ]]; then
|
elif [[ "$DRY_RUN" == true ]]; then
|
||||||
warn "DRY RUN — would remove key blob from $MIRROR:/root/.ssh/authorized_keys"
|
warn "DRY RUN — would remove HOST1 key from $MIRROR:/root/.ssh/authorized_keys"
|
||||||
warn "DRY RUN — would clear ${MIRROR_ID}_PHASE* from $MIRROR:/boot/config/varaverk_setup.db"
|
H1_DONE=true
|
||||||
REMOTE_CLEANED=true
|
|
||||||
else
|
else
|
||||||
timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" \
|
timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" \
|
||||||
-o ConnectTimeout="$SSH_TIMEOUT" -o BatchMode=yes root@"$MIRROR_IP" \
|
-o ConnectTimeout="$SSH_TIMEOUT" -o BatchMode=yes root@"$MIRROR_IP" \
|
||||||
"sed -i \"|${KEY_BLOB}|d\" /root/.ssh/authorized_keys 2>/dev/null
|
"sed -i \"|${KEY_BLOB}|d\" /root/.ssh/authorized_keys 2>/dev/null
|
||||||
sed -i \"/^${MIRROR_ID}_PHASE/d\" /boot/config/varaverk_setup.db 2>/dev/null
|
sed -i \"/^${MIRROR_ID}_PHASE\|^${MIRROR_ID}_KEY_READY/d\" /boot/config/varaverk_setup.db 2>/dev/null
|
||||||
echo ok" 2>/dev/null | grep -q ok && {
|
echo ok" 2>/dev/null | grep -q ok && {
|
||||||
log "HOST1 key removed from $MIRROR authorized_keys ✅"
|
log "HOST1 key removed from $MIRROR authorized_keys ✅"
|
||||||
log "Phase flags cleared on $MIRROR ✅"
|
H1_DONE=true
|
||||||
REMOTE_CLEANED=true
|
} || warn "Could not SSH to $MIRROR — remove HOST1 key there manually"
|
||||||
} || warn "Could not SSH to $MIRROR — remove HOST1 key and phase flags there manually"
|
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Step 2: Delete local SSH key pair ─────────────────────────────────────────
|
# Delete local key pair
|
||||||
echo ""
|
|
||||||
echo "━━━ Step 2 — Delete Local SSH Key ━━━"
|
|
||||||
|
|
||||||
if [[ ! -f "$SSH_KEY" && ! -f "$SSH_KEY_PUB" ]]; then
|
if [[ ! -f "$SSH_KEY" && ! -f "$SSH_KEY_PUB" ]]; then
|
||||||
log "Local key already gone"
|
log "Local key already gone"
|
||||||
LOCAL_KEY_GONE=true
|
|
||||||
elif [[ "$DRY_RUN" == true ]]; then
|
elif [[ "$DRY_RUN" == true ]]; then
|
||||||
warn "DRY RUN — would delete: $SSH_KEY"
|
warn "DRY RUN — would delete: $SSH_KEY and ${SSH_KEY}.pub"
|
||||||
warn "DRY RUN — would delete: $SSH_KEY_PUB"
|
|
||||||
LOCAL_KEY_GONE=true
|
|
||||||
else
|
else
|
||||||
rm -f "$SSH_KEY" "$SSH_KEY_PUB" && {
|
rm -f "$SSH_KEY" "$SSH_KEY_PUB" && log "Local key pair deleted ✅" || \
|
||||||
log "Local key pair deleted ✅"
|
warn "Failed to delete local key — check permissions"
|
||||||
LOCAL_KEY_GONE=true
|
|
||||||
} || warn "Failed to delete $SSH_KEY — check permissions"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Step 3: Clear phase flags from local setup.db ─────────────────────────────
|
# Clear HOST2 phase flags from local setup.db
|
||||||
echo ""
|
if [[ -f "$STATE_FILE" ]]; then
|
||||||
echo "━━━ Step 3 — Clear Phase State ━━━"
|
if [[ "$DRY_RUN" == false ]]; then
|
||||||
|
sed -i "/^${MIRROR_ID}_PHASE/d; /^${MIRROR_ID}_KEY_READY/d" "$STATE_FILE"
|
||||||
if [[ ! -f "$STATE_FILE" ]]; then
|
|
||||||
log "No varaverk_setup.db — nothing to clear"
|
|
||||||
STATE_CLEARED=true
|
|
||||||
elif [[ "$DRY_RUN" == true ]]; then
|
|
||||||
warn "DRY RUN — would remove ${MIRROR_ID}_PHASE* from $STATE_FILE"
|
|
||||||
STATE_CLEARED=true
|
|
||||||
else
|
|
||||||
sed -i "/^${MIRROR_ID}_PHASE/d" "$STATE_FILE" && {
|
|
||||||
log "Phase flags cleared from local setup.db ✅"
|
log "Phase flags cleared from local setup.db ✅"
|
||||||
STATE_CLEARED=true
|
else
|
||||||
}
|
warn "DRY RUN — would clear ${MIRROR_ID}_PHASE* from setup.db"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── HOST2 → HOST1: remove HOST2's key from HOST1's authorized_keys ────────────
|
||||||
|
if [[ "$DIRECTION" == "h2" || "$DIRECTION" == "both" ]]; then
|
||||||
|
echo "━━━ HOST2 → HOST1: Remove $MIRROR key from HOST1 ━━━"
|
||||||
|
|
||||||
|
if [[ ! -f "$AUTH_KEYS" ]]; then
|
||||||
|
log "No authorized_keys on HOST1 — nothing to remove"
|
||||||
|
H2_DONE=true
|
||||||
|
elif ! grep -qi "$MIRROR_SHORT" "$AUTH_KEYS" 2>/dev/null; then
|
||||||
|
log "$MIRROR key not found in HOST1 authorized_keys (already removed or never added)"
|
||||||
|
H2_DONE=true
|
||||||
|
elif [[ "$DRY_RUN" == true ]]; then
|
||||||
|
warn "DRY RUN — would remove $MIRROR_SHORT key from $AUTH_KEYS"
|
||||||
|
H2_DONE=true
|
||||||
|
else
|
||||||
|
sed -i "/${MIRROR_SHORT}/Id" "$AUTH_KEYS" && {
|
||||||
|
log "$MIRROR key removed from HOST1 authorized_keys ✅"
|
||||||
|
H2_DONE=true
|
||||||
|
} || warn "Failed to remove $MIRROR key from HOST1 authorized_keys"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Summary ───────────────────────────────────────────────────────────────────
|
# ── Summary ───────────────────────────────────────────────────────────────────
|
||||||
END=$(date +%s)
|
END=$(date +%s)
|
||||||
echo ""
|
echo "━━━━━ DONE ━━━━━"
|
||||||
echo "━━━━━ CANCEL SUMMARY ━━━━━"
|
[[ "$DIRECTION" == "h1" || "$DIRECTION" == "both" ]] && \
|
||||||
echo " $MIRROR_ID key removed: $( [[ "$REMOTE_CLEANED" == true ]] && echo "✅" || echo "⚠ manual cleanup needed" )"
|
echo " HOST1 → HOST2: $( [[ "$H1_DONE" == true ]] && echo "✅" || echo "⚠ manual step may be needed" )"
|
||||||
echo " Local key deleted: $( [[ "$LOCAL_KEY_GONE" == true ]] && echo "✅" || echo "⚠ still present" )"
|
[[ "$DIRECTION" == "h2" || "$DIRECTION" == "both" ]] && \
|
||||||
echo " Phase state cleared: $( [[ "$STATE_CLEARED" == true ]] && echo "✅" || echo "⚠" )"
|
echo " HOST2 → HOST1: $( [[ "$H2_DONE" == true ]] && echo "✅" || echo "⚠" )"
|
||||||
echo " Duration: $(format_duration $(( END - START )))"
|
echo " Duration: $(format_duration $(( END - START )))"
|
||||||
echo ""
|
|
||||||
if [[ "$REMOTE_CLEANED" == false ]]; then
|
|
||||||
echo " Manual cleanup on $MIRROR:"
|
|
||||||
echo " sed -i '/$(awk "{print \$3}" "$SSH_KEY_PUB" 2>/dev/null || echo "HOST1_key_comment")//d' /root/.ssh/authorized_keys"
|
|
||||||
fi
|
|
||||||
echo " Run Phase 1 again to restart the onboard process."
|
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━"
|
echo "━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
@@ -56,8 +56,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// ── Onboard toggle state — persists across 10s poll re-renders ─────────────────
|
// ── Toggle states — persist across 10s poll re-renders ────────────────────────
|
||||||
const _vvOnboarding = {}; // hostId → true when steps are expanded
|
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
|
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 ──
|
// ── Public action functions — top-level so onclick= attributes can reach them ──
|
||||||
@@ -78,12 +79,22 @@ function _vvPtRun(id, extraArgs) {
|
|||||||
|
|
||||||
function vvPtStartOnboard(hostId) {
|
function vvPtStartOnboard(hostId) {
|
||||||
_vvOnboarding[hostId] = true;
|
_vvOnboarding[hostId] = true;
|
||||||
|
delete _vvDeleteKeys[hostId];
|
||||||
if (_vvPtReload) _vvPtReload();
|
if (_vvPtReload) _vvPtReload();
|
||||||
}
|
}
|
||||||
function vvPtEndOnboard(hostId) {
|
function vvPtEndOnboard(hostId) {
|
||||||
delete _vvOnboarding[hostId];
|
delete _vvOnboarding[hostId];
|
||||||
if (_vvPtReload) _vvPtReload();
|
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) {
|
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;
|
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) {
|
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.disabled = true;
|
||||||
btn.textContent = '⟳ Cancelling…';
|
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')); })
|
.then(d => { if (!d.ok) alert('Failed: ' + (d.error ?? 'Unknown error')); })
|
||||||
.catch(e => alert('Error: ' + e))
|
.catch(e => alert('Error: ' + e))
|
||||||
.finally(() => setTimeout(() => { btn.disabled = false; btn.textContent = '✕ Cancel'; }, 4000));
|
.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) {
|
function vvPtOffboard(btn) {
|
||||||
if (btn.style.opacity === '0.35' || btn.style.cursor === 'default') return;
|
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;
|
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>
|
<span style="font-size:10px;color:${dotCol};">● ${dotLbl}</span>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
|
const isDeleting = !!_vvDeleteKeys[remote.id];
|
||||||
|
|
||||||
// ── Phase 0: not provisioned ─────────────────────────────────────────
|
// ── Phase 0: not provisioned ─────────────────────────────────────────
|
||||||
if (phase === 0) {
|
if (phase === 0) {
|
||||||
if (isOnboarding) {
|
if (isOnboarding) {
|
||||||
// Expanded step-by-step onboard card
|
|
||||||
html += `<div style="background:#0d0d0d;border:1px solid #1e1e1e;border-radius:4px;padding:10px 12px;">
|
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;">
|
<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: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>
|
<span style="font-size:11px;color:#888;">Install SSH key on ${remote.hostname}</span>
|
||||||
</div>
|
</div>
|
||||||
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:4px;">
|
<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;
|
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)})"
|
<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;
|
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;
|
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>
|
white-space:nowrap;cursor:pointer;" title="Click to copy">${termCmd}</code>
|
||||||
</div>
|
</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;">
|
<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: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:11px;color:#888;">Push conf</span>
|
||||||
<span style="font-size:9px;color:#2a2a2a;">if key already installed separately</span>
|
<span style="font-size:9px;color:#2a2a2a;">if key already installed separately</span>
|
||||||
</div>
|
</div>
|
||||||
<button class="vv-pt-action-btn info" onclick="vvPtPushConf(this,'${remote.id}')"
|
<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}">
|
title="Runs --phase1-only --skip-ssh">▶ Push Conf</button>
|
||||||
▶ Push Conf
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="vv-pt-actions" style="margin-top:8px;">
|
<div class="vv-pt-actions" style="margin-top:8px;">
|
||||||
<button class="vv-pt-action-btn warn" onclick="vvPtEndOnboard('${remote.id}')" style="opacity:.7;">
|
<button class="vv-pt-action-btn warn" onclick="vvPtEndOnboard('${remote.id}')" style="opacity:.6;">✕ Close</button>
|
||||||
✕ Close
|
|
||||||
</button>
|
|
||||||
<button class="vv-pt-action-btn warn" onclick="vvPtCancel(this,'${remote.id}')" style="opacity:.6;">
|
|
||||||
✕ Reset keys
|
|
||||||
</button>
|
|
||||||
</div>`;
|
</div>`;
|
||||||
} else {
|
} else {
|
||||||
// Compact row
|
html += `<div style="display:flex;align-items:center;gap:10px;flex-wrap:wrap;">
|
||||||
html += `<div style="display:flex;align-items:center;gap:10px;">
|
|
||||||
<span style="font-size:10px;color:#444;">○ Not provisioned</span>
|
<span style="font-size:10px;color:#444;">○ Not provisioned</span>
|
||||||
<button class="vv-pt-action-btn run" onclick="vvPtStartOnboard('${remote.id}')"
|
<button class="vv-pt-action-btn run" onclick="vvPtStartOnboard('${remote.id}')">▶ Onboard</button>
|
||||||
title="Show initialization steps for ${remote.hostname}">
|
<button class="vv-pt-action-btn warn" onclick="vvPtShowDeleteKeys('${remote.id}')"
|
||||||
▶ Onboard
|
style="opacity:.5;font-size:11px;">🗑 Delete Keys</button>
|
||||||
</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>
|
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Phase 1: SSH done, awaiting HOST2 install ──────────────────────────
|
// ── Phase 1: SSH done, awaiting HOST2 ─────────────────────────────────
|
||||||
} else if (phase === 1) {
|
} else if (phase === 1) {
|
||||||
html += `<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:6px;">
|
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>
|
<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>` : ''}
|
${remote.ts_ip ? `<span style="font-size:9px;color:#2a2a2a;">${remote.ts_ip}</span>` : ''}
|
||||||
</div>
|
</div>
|
||||||
<div style="font-size:10px;color:#444;margin-bottom:8px;">
|
<div style="font-size:10px;color:#444;margin-bottom:8px;">
|
||||||
Waiting for ${remote.id} to install Varaverk and complete its onboard.
|
Waiting for ${remote.id} to install Varaverk and complete its onboard. Phase 2 triggers automatically.
|
||||||
Phase 2 triggers automatically when it does.
|
|
||||||
</div>
|
</div>
|
||||||
<div class="vv-pt-actions">
|
<div class="vv-pt-actions">
|
||||||
<button class="vv-pt-action-btn info" onclick="vvPtPhase2(this,'${remote.id}')"
|
<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">
|
title="Manually trigger Phase 2 if ${remote.id} auto-notification did not arrive">
|
||||||
▶ Run Phase 2 Manually
|
▶ Run Phase 2 Manually
|
||||||
</button>
|
</button>
|
||||||
<button class="vv-pt-action-btn warn" onclick="vvPtCancel(this,'${remote.id}')"
|
<button class="vv-pt-action-btn warn" onclick="vvPtCancel(this,'${remote.id}')">✕ Cancel</button>
|
||||||
style="opacity:.7;">
|
<button class="vv-pt-action-btn warn" onclick="vvPtShowDeleteKeys('${remote.id}')"
|
||||||
✕ Cancel
|
style="opacity:.6;font-size:11px;">🗑 Delete Keys</button>
|
||||||
</button>
|
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
// ── Phase 2: fully onboarded ───────────────────────────────────────────
|
// ── Phase 2: fully onboarded ───────────────────────────────────────────
|
||||||
@@ -434,9 +448,38 @@ function _renderActions(nodes, cfg) {
|
|||||||
</div>
|
</div>
|
||||||
<div class="vv-pt-actions">
|
<div class="vv-pt-actions">
|
||||||
<button class="vv-pt-action-btn info" onclick="vvPtPhase2(this,'${remote.id}')"
|
<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">
|
style="opacity:.4;" title="Re-run Phase 2">↻ Re-run Phase 2</button>
|
||||||
↻ Re-run Phase 2
|
<button class="vv-pt-action-btn warn" onclick="vvPtShowDeleteKeys('${remote.id}')"
|
||||||
</button>
|
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>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user