Merge pull request #203 from writefreely/T319-admin-delete-acct
T319 admin delete acct
This commit is contained in:
commit
4e0912b32a
8 changed files with 143 additions and 16 deletions
33
admin.go
33
admin.go
|
@ -189,6 +189,7 @@ func handleViewAdminUsers(app *App, u *User, w http.ResponseWriter, r *http.Requ
|
||||||
*AdminPage
|
*AdminPage
|
||||||
Config config.AppCfg
|
Config config.AppCfg
|
||||||
Message string
|
Message string
|
||||||
|
Flashes []string
|
||||||
|
|
||||||
Users *[]User
|
Users *[]User
|
||||||
CurPage int
|
CurPage int
|
||||||
|
@ -201,6 +202,7 @@ func handleViewAdminUsers(app *App, u *User, w http.ResponseWriter, r *http.Requ
|
||||||
Message: r.FormValue("m"),
|
Message: r.FormValue("m"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.Flashes, _ = getSessionFlashes(app, w, r, nil)
|
||||||
p.TotalUsers = app.db.GetAllUsersCount()
|
p.TotalUsers = app.db.GetAllUsersCount()
|
||||||
ttlPages := p.TotalUsers / adminUsersPerPage
|
ttlPages := p.TotalUsers / adminUsersPerPage
|
||||||
p.TotalPages = []int{}
|
p.TotalPages = []int{}
|
||||||
|
@ -312,6 +314,37 @@ func handleViewAdminUser(app *App, u *User, w http.ResponseWriter, r *http.Reque
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleAdminDeleteUser(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
|
||||||
|
if !u.IsAdmin() {
|
||||||
|
return impart.HTTPError{http.StatusForbidden, "Administrator privileges required for this action"}
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
username := vars["username"]
|
||||||
|
confirmUsername := r.PostFormValue("confirm-username")
|
||||||
|
|
||||||
|
if confirmUsername != username {
|
||||||
|
return impart.HTTPError{http.StatusBadRequest, "Username was not confirmed"}
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := app.db.GetUserForAuth(username)
|
||||||
|
if err == ErrUserNotFound {
|
||||||
|
return impart.HTTPError{http.StatusNotFound, fmt.Sprintf("User '%s' was not found", username)}
|
||||||
|
} else if err != nil {
|
||||||
|
log.Error("get user for deletion: %v", err)
|
||||||
|
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not get user with username '%s': %v", username, err)}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = app.db.DeleteAccount(user.ID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("delete user %s: %v", user.Username, err)
|
||||||
|
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not delete user account for '%s': %v", username, err)}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = addSessionFlash(app, w, r, fmt.Sprintf("User \"%s\" was deleted successfully.", username), nil)
|
||||||
|
return impart.HTTPError{http.StatusFound, "/admin/users"}
|
||||||
|
}
|
||||||
|
|
||||||
func handleAdminToggleUserStatus(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
|
func handleAdminToggleUserStatus(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
username := vars["username"]
|
username := vars["username"]
|
||||||
|
|
|
@ -1044,6 +1044,19 @@ li {
|
||||||
background-color: #dff0d8;
|
background-color: #dff0d8;
|
||||||
border-color: #d6e9c6;
|
border-color: #d6e9c6;
|
||||||
}
|
}
|
||||||
|
&.danger {
|
||||||
|
border-color: #856404;
|
||||||
|
background-color: white;
|
||||||
|
h3 {
|
||||||
|
margin: 0 0 0.5em 0;
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: bold;
|
||||||
|
color: black !important;
|
||||||
|
}
|
||||||
|
h3 + p, button {
|
||||||
|
font-size: 0.86em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
|
@ -340,6 +340,15 @@ body#pad {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
line-height: 1.5;
|
||||||
|
|
||||||
|
input[type=text].confirm {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.short {
|
.short {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
|
@ -166,6 +166,7 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router {
|
||||||
write.HandleFunc("/admin/settings", handler.Admin(handleViewAdminSettings)).Methods("GET")
|
write.HandleFunc("/admin/settings", handler.Admin(handleViewAdminSettings)).Methods("GET")
|
||||||
write.HandleFunc("/admin/users", handler.Admin(handleViewAdminUsers)).Methods("GET")
|
write.HandleFunc("/admin/users", handler.Admin(handleViewAdminUsers)).Methods("GET")
|
||||||
write.HandleFunc("/admin/user/{username}", handler.Admin(handleViewAdminUser)).Methods("GET")
|
write.HandleFunc("/admin/user/{username}", handler.Admin(handleViewAdminUser)).Methods("GET")
|
||||||
|
write.HandleFunc("/admin/user/{username}/delete", handler.Admin(handleAdminDeleteUser)).Methods("POST")
|
||||||
write.HandleFunc("/admin/user/{username}/status", handler.Admin(handleAdminToggleUserStatus)).Methods("POST")
|
write.HandleFunc("/admin/user/{username}/status", handler.Admin(handleAdminToggleUserStatus)).Methods("POST")
|
||||||
write.HandleFunc("/admin/user/{username}/passphrase", handler.Admin(handleAdminResetUserPass)).Methods("POST")
|
write.HandleFunc("/admin/user/{username}/passphrase", handler.Admin(handleAdminResetUserPass)).Methods("POST")
|
||||||
write.HandleFunc("/admin/pages", handler.Admin(handleViewAdminPages)).Methods("GET")
|
write.HandleFunc("/admin/pages", handler.Admin(handleViewAdminPages)).Methods("GET")
|
||||||
|
|
24
static/js/modals.js
Normal file
24
static/js/modals.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2016-2021 A Bunch Tell LLC.
|
||||||
|
*
|
||||||
|
* This file is part of WriteFreely.
|
||||||
|
*
|
||||||
|
* WriteFreely is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License, included
|
||||||
|
* in the LICENSE file in this source code package.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function showModal(id) {
|
||||||
|
document.getElementById('overlay').style.display = 'block';
|
||||||
|
document.getElementById('modal-'+id).style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
var closeModals = function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
document.getElementById('overlay').style.display = 'none';
|
||||||
|
var modals = document.querySelectorAll('.modal');
|
||||||
|
for (var i=0; i<modals.length; i++) {
|
||||||
|
modals[i].style.display = 'none';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
H.getEl('overlay').on('click', closeModals);
|
|
@ -4,6 +4,12 @@
|
||||||
<div class="snug content-container">
|
<div class="snug content-container">
|
||||||
{{template "admin-header" .}}
|
{{template "admin-header" .}}
|
||||||
|
|
||||||
|
<!-- TODO: if other use for flashes use patern like account_import.go -->
|
||||||
|
{{if .Flashes}}
|
||||||
|
<p class="alert success">
|
||||||
|
{{range .Flashes}}{{.}}{{end}}
|
||||||
|
</p>
|
||||||
|
{{end}}
|
||||||
<div class="row admin-actions" style="justify-content: space-between;">
|
<div class="row admin-actions" style="justify-content: space-between;">
|
||||||
<span style="font-style: italic; font-size: 1.2em">{{.TotalUsers}} {{pluralize "user" "users" .TotalUsers}}</span>
|
<span style="font-style: italic; font-size: 1.2em">{{.TotalUsers}} {{pluralize "user" "users" .TotalUsers}}</span>
|
||||||
<a class="btn cta" href="/me/invites">+ Invite people</a>
|
<a class="btn cta" href="/me/invites">+ Invite people</a>
|
||||||
|
|
|
@ -32,8 +32,13 @@ input.copy-text {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
.modal {
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<div class="snug content-container">
|
<div class="snug content-container">
|
||||||
|
<div id="overlay"></div>
|
||||||
|
|
||||||
{{template "admin-header" .}}
|
{{template "admin-header" .}}
|
||||||
|
|
||||||
<h2 id="posts-header">{{.User.Username}}</h2>
|
<h2 id="posts-header">{{.User.Username}}</h2>
|
||||||
|
@ -139,9 +144,60 @@ input.copy-text {
|
||||||
{{end}}
|
{{end}}
|
||||||
</table>
|
</table>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
{{ if not .User.IsAdmin }}
|
||||||
|
<h2>Incinerator</h2>
|
||||||
|
<div class="alert danger">
|
||||||
|
<div class="row">
|
||||||
|
<div>
|
||||||
|
<h3>Delete this user</h3>
|
||||||
|
<p>Permanently erase all user data, with no way to recover it.</p>
|
||||||
|
</div>
|
||||||
|
<button class="cta danger" onclick="prepareDeleteUser()">Delete this user...</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="modal-delete-user" class="modal">
|
||||||
|
<h2>Are you sure?</h2>
|
||||||
|
<div class="body">
|
||||||
|
<p style="text-align:left">This action <strong>cannot</strong> be undone. It will permanently erase all traces of this user, <strong>{{.User.Username}}</strong>, including their account information, blogs, and posts.</p>
|
||||||
|
<p>Please type <strong>{{.User.Username}}</strong> to confirm.</p>
|
||||||
|
|
||||||
|
<ul id="delete-errors" class="errors"></ul>
|
||||||
|
|
||||||
|
<form action="/admin/user/{{.User.Username}}/delete" method="post" onsubmit="confirmDeletion()">
|
||||||
|
<input id="confirm-text" placeholder="{{.User.Username}}" type="text" class="confirm boxy" name="confirm-username" style="margin-top: 0.5em;" />
|
||||||
|
<div style="text-align:right; margin-top: 1em;">
|
||||||
|
<a id="cancel-delete" style="margin-right:2em" href="#">Cancel</a>
|
||||||
|
<input class="danger" type="submit" id="confirm-delete" value="Delete this user" disabled />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/js/h.js"></script>
|
||||||
|
<script src="/js/modals.js"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
H.getEl('cancel-delete').on('click', closeModals);
|
||||||
|
|
||||||
|
let $confirmDelBtn = document.getElementById('confirm-delete');
|
||||||
|
let $confirmText = document.getElementById('confirm-text')
|
||||||
|
$confirmText.addEventListener('input', function() {
|
||||||
|
$confirmDelBtn.disabled = this.value !== '{{.User.Username}}'
|
||||||
|
});
|
||||||
|
|
||||||
|
function prepareDeleteUser() {
|
||||||
|
$confirmText.value = ''
|
||||||
|
showModal('delete-user')
|
||||||
|
$confirmText.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmDeletion() {
|
||||||
|
$confirmDelBtn.disabled = true
|
||||||
|
$confirmDelBtn.value = 'Deleting...'
|
||||||
|
}
|
||||||
|
|
||||||
function confirmSilence() {
|
function confirmSilence() {
|
||||||
return confirm("Silence this user? They'll still be able to log in and access their posts, but no one else will be able to see them anymore. You can reverse this decision at any time.");
|
return confirm("Silence this user? They'll still be able to log in and access their posts, but no one else will be able to see them anymore. You can reverse this decision at any time.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -193,25 +193,10 @@ textarea.section.norm {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/js/h.js"></script>
|
<script src="/js/h.js"></script>
|
||||||
|
<script src="/js/modals.js"></script>
|
||||||
<script src="/js/ace.js" type="text/javascript" charset="utf-8"></script>
|
<script src="/js/ace.js" type="text/javascript" charset="utf-8"></script>
|
||||||
<script>
|
<script>
|
||||||
// Begin shared modal code
|
|
||||||
function showModal(id) {
|
|
||||||
document.getElementById('overlay').style.display = 'block';
|
|
||||||
document.getElementById('modal-'+id).style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
var closeModals = function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
document.getElementById('overlay').style.display = 'none';
|
|
||||||
var modals = document.querySelectorAll('.modal');
|
|
||||||
for (var i=0; i<modals.length; i++) {
|
|
||||||
modals[i].style.display = 'none';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
H.getEl('overlay').on('click', closeModals);
|
|
||||||
H.getEl('cancel-delete').on('click', closeModals);
|
H.getEl('cancel-delete').on('click', closeModals);
|
||||||
// end
|
|
||||||
var deleteBlog = function(e) {
|
var deleteBlog = function(e) {
|
||||||
if (document.getElementById('confirm-text').value != '{{.Alias}}') {
|
if (document.getElementById('confirm-text').value != '{{.Alias}}') {
|
||||||
document.getElementById('delete-errors').innerHTML = '<li class="urgent">Enter <strong>{{.Alias}}</strong> in the box below.</li>';
|
document.getElementById('delete-errors').innerHTML = '<li class="urgent">Enter <strong>{{.Alias}}</strong> in the box below.</li>';
|
||||||
|
|
Loading…
Add table
Reference in a new issue