ReviewOS

also looking at this

stacks/ts-cloud

fix(dashboard): confirm destructive actions that ran on first click

#130
Merged glennmichael123 wants to merge fix/dashboard-destructive-confirms into main
4 files +99 -10
packages/ui/pages/server/team.stxmodified+23-1
Changes to packages/ui/pages/server/team.stx
@@ -67,6 +67,22 @@ async function revoke(name) {
6767 load()
6868}
6969
70// Removal runs through a typed-confirm bar (same pattern as firewall/secrets):
71// type the username to confirm. Revoking pulls someone's access to every site
72// on the box at once and invalidates their password, so it should never be one
73// misclick away.
74const pendingRevoke = state(null)
75const typedRevoke = state('')
76const canRevoke = derived(() => { const u = pendingRevoke(); return u !== null && typedRevoke() === String(u.username) })
77function askRevoke(user) { pendingRevoke.set(user); typedRevoke.set('') }
78function cancelRevoke() { pendingRevoke.set(null); typedRevoke.set('') }
79async function confirmRevoke() {
80 const u = pendingRevoke()
81 if (!u || typedRevoke() !== String(u.username)) return
82 pendingRevoke.set(null); typedRevoke.set('')
83 await revoke(u.username)
84}
85
7086onMount(() => { load() })
7187</script>
7288<!DOCTYPE html>
@@ -135,12 +151,18 @@ onMount(() => { load() })
135151 <td><b>{{ u.name }}</b><span class="mono uname">{{ u.username }}</span></td>
136152 <td><span class="tag">{{ u.role === 'admin' ? 'box owner' : 'member' }}</span></td>
137153 <td class="mono">{{ u.role === 'admin' ? 'every site' : siteList(u) }}</td>
138 <td class="table-actions"><button type="button" class="btn danger sm" @click="revoke(u.username)">Remove</button></td>
154 <td class="table-actions"><button type="button" class="btn danger sm" @click="askRevoke(u)">Remove</button></td>
139155 </tr>
140156 </template>
141157 </tbody>
142158 </table>
143159 <div class="compact empty" @show="users().length === 0"><strong>No one invited yet</strong><span>Invite someone above to give them access to a site.</span></div>
160 <div class="op-confirm" @show="pendingRevoke() !== null" style="margin-top:14px">
161 <span>Type <b class="mono">{{ pendingRevoke()?.username }}</b> to remove <b>{{ pendingRevoke()?.name }}</b> from every site:</span>
162 <input class="op-confirm-input" :value="typedRevoke()" @input="typedRevoke.set($event.target.value)" @keydown.enter="confirmRevoke()" placeholder="confirm" autocomplete="off">
163 <button class="btn danger sm" :disabled="!canRevoke()" @click="confirmRevoke()">Remove</button>
164 <button class="btn ghost sm" @click="cancelRevoke()">Cancel</button>
165 </div>
144166 <p class="note">Box owners reach everything on this server. Members reach only the sites listed here, and never the shell, SSH keys, firewall or databases. Access is checked on every request.</p>
145167 </div>
146168 </div>