also looking at this
fix(dashboard): confirm destructive actions that ran on first click
#130
4 files
+99
-10
| @@ -38,6 +38,21 @@ async function removeKey(n) { | ||
| 38 | 38 | keys.set(data.keys || []) |
| 39 | 39 | setMessage('Removed from cloud config. Run cloud deploy to apply.', 'ok') |
| 40 | 40 | } |
| 41 | // Removal runs through a typed-confirm bar (same pattern as firewall/team): | |
| 42 | // type the key name to confirm. Removing the wrong key can lock every operator | |
| 43 | // out of the box, so it should never be one misclick away. | |
| 44 | const pendingRemove = state(null) | |
| 45 | const typedRemove = state('') | |
| 46 | const canRemove = derived(() => { const k = pendingRemove(); return k !== null && typedRemove() === String(k.name) }) | |
| 47 | function askRemove(key) { pendingRemove.set(key); typedRemove.set('') } | |
| 48 | function cancelRemove() { pendingRemove.set(null); typedRemove.set('') } | |
| 49 | async function confirmRemove() { | |
| 50 | const k = pendingRemove() | |
| 51 | if (!k || typedRemove() !== String(k.name)) return | |
| 52 | pendingRemove.set(null); typedRemove.set('') | |
| 53 | await removeKey(k.name) | |
| 54 | } | |
| 55 | ||
| 41 | 56 | onMount(() => { load() }) |
| 42 | 57 | </script> |
| 43 | 58 | <!DOCTYPE html> |
| @@ -79,12 +94,18 @@ onMount(() => { load() }) | ||
| 79 | 94 | <td class="mono" style="color:var(--txt3)">{{ k.type }}</td> |
| 80 | 95 | <td class="mono">{{ k.fingerprint }}</td> |
| 81 | 96 | <td style="color:var(--txt3)">{{ k.added }}</td> |
| 82 | <td class="table-actions"><button type="button" class="btn danger sm" @click="removeKey(k.name)">Remove</button></td> | |
| 97 | <td class="table-actions"><button type="button" class="btn danger sm" :aria-label="'Remove SSH key ' + k.name" @click="askRemove(k)">Remove</button></td> | |
| 83 | 98 | </tr> |
| 84 | 99 | </template> |
| 85 | 100 | </tbody> |
| 86 | 101 | </table> |
| 87 | 102 | <div class="compact empty" @show="keys().length === 0"><strong>No SSH keys configured</strong><span>Add a public key above to update <span class="mono">infrastructure.compute.sshKeys</span>.</span></div> |
| 103 | <div class="op-confirm" @show="pendingRemove() !== null" style="margin-top:14px"> | |
| 104 | <span>Type <b class="mono">{{ pendingRemove()?.name }}</b> to remove this key from the box:</span> | |
| 105 | <input class="op-confirm-input" :value="typedRemove()" @input="typedRemove.set($event.target.value)" @keydown.enter="confirmRemove()" placeholder="confirm" autocomplete="off" aria-label="Type the key name to confirm removal"> | |
| 106 | <button type="button" class="btn danger sm" :disabled="!canRemove()" @click="confirmRemove()">Remove</button> | |
| 107 | <button type="button" class="btn ghost sm" @click="cancelRemove()">Cancel</button> | |
| 108 | </div> | |
| 88 | 109 | <p class="note">Keys are declarative: this page edits <span class="mono">infrastructure.compute.sshKeys</span> in cloud config. Run <span class="mono">cloud deploy</span> to apply changes to the box. Connect with <span class="mono">cloud server:ssh {{ server.name }}</span>.</p> |
| 89 | 110 | </div> |
| 90 | 111 | </div> |