mirror of https://github.com/zulip/zulip.git
Add administrative panel to allow for user deactivations etc.
We now show a list of users and allow you to deactivate a user using the same process as `python manage.py deactivate_user`. We add a new menu item accessible from the gear icon which will eventually have much more than just this, but we have a good start here. Here we also add a property to UserProfile which determines whether you're eligible to access the administration panel, and then have code which shows the menu option if so. This introduces a new JS file, admin.js. (imported from commit 52296fdedb46b4f32d541df43022ffccfb277297)
This commit is contained in:
parent
78d8153e6b
commit
ecc42bc9f8
|
@ -0,0 +1,70 @@
|
||||||
|
var admin = (function () {
|
||||||
|
|
||||||
|
var exports = {};
|
||||||
|
|
||||||
|
function populate_users () {
|
||||||
|
var tb = $("#admin_users_table");
|
||||||
|
tb.empty();
|
||||||
|
page_params.people_list.sort(function (a, b) {
|
||||||
|
return a.full_name.toLowerCase().localeCompare(b.full_name.toLowerCase());
|
||||||
|
});
|
||||||
|
|
||||||
|
$.each(page_params.people_list, function (index, person) {
|
||||||
|
if (person.email.indexOf("+") === -1 && person.email.indexOf("-bot@") === -1) {
|
||||||
|
tb.append(templates.render("admin_user_list", {person: person}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.setup_page = function () {
|
||||||
|
populate_users();
|
||||||
|
|
||||||
|
$("#admin_users_table").on("click", ".activation_toggle_button", function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
$(".active_user_row").removeClass("active_user_row");
|
||||||
|
|
||||||
|
// Go up the tree until we find the user row, then grab the email element
|
||||||
|
$(e.target).closest(".user_row").addClass("active_user_row");
|
||||||
|
|
||||||
|
var user_name = $(".active_user_row").find('.user_name').text();
|
||||||
|
var email = $(".active_user_row").find('.email').text();
|
||||||
|
|
||||||
|
$("#deactivation_modal .email").text(email);
|
||||||
|
$("#deactivation_modal .user_name").text(user_name);
|
||||||
|
$("#deactivation_modal").modal("show");
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#do_deactivate_button").click(function (e) {
|
||||||
|
if ($("#deactivation_modal .email").html() !== $(".active_user_row").find('.email').text()) {
|
||||||
|
blueslip.error("User deactivation canceled due to non-matching fields.");
|
||||||
|
ui.report_message("Deactivation encountered an error. Please reload and try again.",
|
||||||
|
$("#home-error"), 'alert-error');
|
||||||
|
}
|
||||||
|
$("#deactivation_modal").modal("hide");
|
||||||
|
$(".active_user_row button").prop("disabled", true).text("Working…");
|
||||||
|
$.ajax({
|
||||||
|
type: 'DELETE',
|
||||||
|
url: '/json/users/' + $(".active_user_row").find('.email').text(),
|
||||||
|
error: function (xhr, error_type) {
|
||||||
|
if (xhr.status.toString().charAt(0) === "4") {
|
||||||
|
$(".active_user_row button").closest("td").html(
|
||||||
|
$("<p>").addClass("text-error").text($.parseJSON(xhr.responseText).msg)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$(".active_user_row button").text("Failed!");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
success: function () {
|
||||||
|
$(".active_user_row button").removeClass("btn-danger").text("Deactivated");
|
||||||
|
$(".active_user_row span").wrap("<strike>");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
return exports;
|
||||||
|
|
||||||
|
}());
|
|
@ -130,6 +130,9 @@ function do_hashchange() {
|
||||||
case "#subscriptions":
|
case "#subscriptions":
|
||||||
ui.change_tab_to("#subscriptions");
|
ui.change_tab_to("#subscriptions");
|
||||||
break;
|
break;
|
||||||
|
case "#administration":
|
||||||
|
ui.change_tab_to("#administration");
|
||||||
|
break;
|
||||||
case "#settings":
|
case "#settings":
|
||||||
ui.change_tab_to("#settings");
|
ui.change_tab_to("#settings");
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -858,6 +858,9 @@ $(function () {
|
||||||
// Whenever the streams page comes up (from anywhere), populate it.
|
// Whenever the streams page comes up (from anywhere), populate it.
|
||||||
subs_link.on('shown', subs.setup_page);
|
subs_link.on('shown', subs.setup_page);
|
||||||
|
|
||||||
|
var admin_link = $('#gear-menu a[href="#administration"]');
|
||||||
|
admin_link.on('shown', admin.setup_page);
|
||||||
|
|
||||||
$('#pw_change_link').on('click', function (e) {
|
$('#pw_change_link').on('click', function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
$('#pw_change_link').hide();
|
$('#pw_change_link').hide();
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
{{#with person}}
|
||||||
|
<tr class="user_row" id="user_{{email}}">
|
||||||
|
<td>
|
||||||
|
<span class="user_name">{{full_name}}</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="email">{{email}}</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn activation_toggle_button btn-danger"
|
||||||
|
{{#inactive}}disabled{{/inactive}}
|
||||||
|
type="button" name="activation_toggle">
|
||||||
|
{{#inactive}}
|
||||||
|
Reactivate
|
||||||
|
{{/inactive}}
|
||||||
|
{{^inactive}}
|
||||||
|
Deactivate
|
||||||
|
{{/inactive}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{/with}}
|
|
@ -0,0 +1,32 @@
|
||||||
|
{# Administration panel #}
|
||||||
|
|
||||||
|
<div class="row-fluid">
|
||||||
|
<div class="span12">
|
||||||
|
<div class="administration">
|
||||||
|
<h1>Administration</h1>
|
||||||
|
<h2>Users</h2>
|
||||||
|
<table class="table table-condensed table-striped">
|
||||||
|
<tbody id="admin_users_table">
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Email</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div id="deactivation_modal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="deactivation_modal_label" aria-hidden="true">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
<h3 id="deactivation_modal_label">Deactivate <span class="email"></span></h3>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>By deactivating <strong><span class="user_name"></span></strong> <<span class="email"></span>>, they will be logged out of Zulip immediately.</p>
|
||||||
|
<p>Their password will be cleared from our systems, and any bots they maintain will be disabled.</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn" data-dismiss="modal" aria-hidden="true">Cancel</button>
|
||||||
|
<button class="btn btn-danger" id="do_deactivate_button">Deactivate now</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
|
@ -51,6 +51,11 @@ var page_params = {{ page_params }};
|
||||||
<div class="tab-pane" id="subscriptions">
|
<div class="tab-pane" id="subscriptions">
|
||||||
{% include "zerver/subscriptions.html" %}
|
{% include "zerver/subscriptions.html" %}
|
||||||
</div>
|
</div>
|
||||||
|
{% if show_admin %}
|
||||||
|
<div class="tab-pane" id="administration">
|
||||||
|
{% include "zephyr/administration.html" %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div class="tab-pane" id="settings">
|
<div class="tab-pane" id="settings">
|
||||||
{% include "zerver/settings.html" %}
|
{% include "zerver/settings.html" %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -58,6 +58,13 @@
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="divider"></li>
|
<li class="divider"></li>
|
||||||
|
{% if show_admin %}
|
||||||
|
<li title="Administration">
|
||||||
|
<a href="#administration" role="button" data-toggle="tab">
|
||||||
|
<i class="icon-vector-bolt"></i> Administration
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
{% if show_invites %}
|
{% if show_invites %}
|
||||||
<li title="Invite coworkers to Zulip">
|
<li title="Invite coworkers to Zulip">
|
||||||
<a href="#invite-user" role="button" data-toggle="modal">
|
<a href="#invite-user" role="button" data-toggle="modal">
|
||||||
|
|
|
@ -26,7 +26,7 @@ var globals =
|
||||||
+ ' invite ui util activity timerender MessageList blueslip unread stream_list'
|
+ ' invite ui util activity timerender MessageList blueslip unread stream_list'
|
||||||
+ ' onboarding message_edit tab_bar emoji popovers navigate message_tour'
|
+ ' onboarding message_edit tab_bar emoji popovers navigate message_tour'
|
||||||
+ ' avatar feature_flags search_suggestion referral stream_color Dict'
|
+ ' avatar feature_flags search_suggestion referral stream_color Dict'
|
||||||
+ ' Filter summary'
|
+ ' Filter summary admin'
|
||||||
|
|
||||||
// colorspace.js
|
// colorspace.js
|
||||||
+ ' colorspace'
|
+ ' colorspace'
|
||||||
|
|
|
@ -150,6 +150,14 @@ class UserProfile(AbstractBaseUser, PermissionsMixin):
|
||||||
|
|
||||||
objects = UserManager()
|
objects = UserManager()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def show_admin(self):
|
||||||
|
# Logic to determine if the user should see the administration tools.
|
||||||
|
# Do NOT use this to check if a user is authorized to perform a specific action!
|
||||||
|
return 0 < self.userobjectpermission_set.filter(
|
||||||
|
content_type__name="realm",
|
||||||
|
permission__codename="administer").count()
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return (u"<UserProfile: %s %s>" % (self.email, self.realm)).encode("utf-8")
|
return (u"<UserProfile: %s %s>" % (self.email, self.realm)).encode("utf-8")
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|
|
@ -659,7 +659,8 @@ def home(request):
|
||||||
'nofontface': is_buggy_ua(request.META["HTTP_USER_AGENT"]),
|
'nofontface': is_buggy_ua(request.META["HTTP_USER_AGENT"]),
|
||||||
'show_debug':
|
'show_debug':
|
||||||
settings.DEBUG and ('show_debug' in request.GET),
|
settings.DEBUG and ('show_debug' in request.GET),
|
||||||
'show_invites': show_invites
|
'show_invites': show_invites,
|
||||||
|
'show_admin': user_profile.show_admin,
|
||||||
},
|
},
|
||||||
context_instance=RequestContext(request))
|
context_instance=RequestContext(request))
|
||||||
|
|
||||||
|
|
|
@ -378,6 +378,7 @@ JS_SPECS = {
|
||||||
'js/compose_fade.js',
|
'js/compose_fade.js',
|
||||||
'js/compose.js',
|
'js/compose.js',
|
||||||
'js/stream_color.js',
|
'js/stream_color.js',
|
||||||
|
'js/admin.js',
|
||||||
'js/subs.js',
|
'js/subs.js',
|
||||||
'js/message_edit.js',
|
'js/message_edit.js',
|
||||||
'js/ui.js',
|
'js/ui.js',
|
||||||
|
|
Loading…
Reference in New Issue