const settings_data = require("./settings_data"); const render_admin_user_list = require("../templates/admin_user_list.hbs"); const render_bot_owner_select = require("../templates/bot_owner_select.hbs"); const render_user_info_form_modal = require('../templates/user_info_form_modal.hbs'); const meta = { loaded: false, }; exports.reset = function () { meta.loaded = false; }; function compare_a_b(a, b) { if (a > b) { return 1; } else if (a === b) { return 0; } return -1; } function sort_email(a, b) { const email_a = settings_data.email_for_user_settings(a) || ''; const email_b = settings_data.email_for_user_settings(b) || ''; return compare_a_b( email_a.toLowerCase(), email_b.toLowerCase() ); } function sort_role(a, b) { function role(user) { if (user.is_admin) { return 0; } if (user.is_guest) { return 2; } return 1; // member } return compare_a_b(role(a), role(b)); } function sort_bot_owner(a, b) { function owner_name(item) { const owner = people.get_bot_owner_user(item); if (!owner) { return ''; } if (!owner.full_name) { return ''; } return owner.full_name.toLowerCase(); } return compare_a_b( owner_name(a), owner_name(b) ); } function sort_last_active(a, b) { return compare_a_b( presence.last_active_date(a.id) || 0, presence.last_active_date(b.id) || 0 ); } function get_user_info_row(user_id) { return $("tr.user_row[data-user-id='" + user_id + "']"); } function update_view_on_deactivate(row) { const button = row.find("button.deactivate"); const user_role = row.find(".user_role"); button.prop("disabled", false); row.find('button.open-user-form').hide(); row.find('i.deactivated-user-icon').show(); button.addClass("btn-warning reactivate"); button.removeClass("deactivate btn-danger"); button.text(i18n.t("Reactivate")); row.addClass("deactivated_user"); if (user_role) { const user_id = row.data('user-id'); user_role.text("%state (%role)".replace("%state", i18n.t("Deactivated")). replace("%role", people.get_user_type(user_id))); } } function update_view_on_reactivate(row) { const button = row.find("button.reactivate"); const user_role = row.find(".user_role"); row.find("button.open-user-form").show(); row.find('i.deactivated-user-icon').hide(); button.addClass("btn-danger deactivate"); button.removeClass("btn-warning reactivate"); button.text(i18n.t("Deactivate")); row.removeClass("deactivated_user"); if (user_role) { const user_id = row.data('user-id'); user_role.text(people.get_user_type(user_id)); } } function get_status_field() { const current_tab = settings_panel_menu.org_settings.current_tab(); switch (current_tab) { case 'deactivated-users-admin': return $("#deactivated-user-field-status").expectOne(); case 'user-list-admin': return $("#user-field-status").expectOne(); case 'bot-list-admin': return $("#bot-field-status").expectOne(); default: blueslip.fatal("Invalid admin settings page"); } } exports.update_user_data = function (user_id, new_data) { if (!meta.loaded) { return; } const user_row = get_user_info_row(user_id); if (new_data.full_name !== undefined) { // Update the full name in the table user_row.find(".user_name").text(new_data.full_name); } if (new_data.owner !== undefined) { // Update the bot owner in the table user_row.find(".owner").text(new_data.owner); } if (new_data.is_active !== undefined) { if (new_data.is_active === false) { // Deactivate the user/bot in the table update_view_on_deactivate(user_row); } else { // Reactivate the user/bot in the table update_view_on_reactivate(user_row); } } if (new_data.is_admin !== undefined || new_data.is_guest !== undefined) { user_row.find(".user_role").text(people.get_user_type(user_id)); } }; function failed_listing_users(xhr) { loading.destroy_indicator($('#subs_page_loading_indicator')); const status = get_status_field(); ui_report.error(i18n.t("Error listing users or bots"), xhr, status); } function populate_users(realm_people_data) { let active_users = []; let deactivated_users = []; let bots = []; for (const user of realm_people_data.members) { user.is_active_human = user.is_active && !user.is_bot; if (user.is_bot) { // Convert bot type id to string for viewing to the users. user.bot_type = settings_bots.type_id_to_string(user.bot_type); const bot_owner = people.get_bot_owner_user(user); if (bot_owner) { user.bot_owner_full_name = bot_owner.full_name; } else { user.no_owner = true; user.bot_owner_full_name = i18n.t("No owner"); } bots.push(user); } else if (user.is_active) { active_users.push(user); } else { deactivated_users.push(user); } } active_users = _.sortBy(active_users, 'full_name'); deactivated_users = _.sortBy(deactivated_users, 'full_name'); bots = _.sortBy(bots, 'full_name'); const reset_scrollbar = function ($sel) { return function () { ui.reset_scrollbar($sel); }; }; const $bots_table = $("#admin_bots_table"); list_render.create($bots_table, bots, { name: "admin_bot_list", modifier: function (item) { return render_admin_user_list({ can_modify: page_params.is_admin, // It's always safe to show the fake email addresses for bot users display_email: item.email, user: item, }); }, filter: { element: $bots_table.closest(".settings-section").find(".search"), predicate: function (item, value) { return item.full_name.toLowerCase().includes(value) || item.email.toLowerCase().includes(value); }, onupdate: reset_scrollbar($bots_table), }, parent_container: $("#admin-bot-list").expectOne(), init_sort: ['alphabetic', 'full_name'], sort_fields: { email: sort_email, bot_owner: sort_bot_owner, }, }); function get_last_active(user) { const last_active_date = presence.last_active_date(user.user_id); if (!last_active_date) { return i18n.t("Unknown"); } return timerender.render_now(last_active_date).time_str; } const $users_table = $("#admin_users_table"); list_render.create($users_table, active_users, { name: "users_table_list", modifier: function (item) { return render_admin_user_list({ can_modify: page_params.is_admin, is_current_user: people.is_my_user_id(item.user_id), display_email: settings_data.email_for_user_settings(item), user: item, last_active_date: get_last_active(item), }); }, filter: { element: $users_table.closest(".settings-section").find(".search"), filterer: people.filter_for_user_settings_search, onupdate: reset_scrollbar($users_table), }, parent_container: $("#admin-user-list").expectOne(), init_sort: ['alphabetic', 'full_name'], sort_fields: { email: sort_email, last_active: sort_last_active, role: sort_role, }, }); const $deactivated_users_table = $("#admin_deactivated_users_table"); list_render.create($deactivated_users_table, deactivated_users, { name: "deactivated_users_table_list", modifier: function (item) { return render_admin_user_list({ user: item, display_email: settings_data.email_for_user_settings(item), can_modify: page_params.is_admin, }); }, filter: { element: $deactivated_users_table.closest(".settings-section").find(".search"), filterer: people.filter_for_user_settings_search, onupdate: reset_scrollbar($deactivated_users_table), }, parent_container: $("#admin-deactivated-users-list").expectOne(), init_sort: ['alphabetic', 'full_name'], sort_fields: { email: sort_email, role: sort_role, }, }); loading.destroy_indicator($('#admin_page_users_loading_indicator')); loading.destroy_indicator($('#admin_page_bots_loading_indicator')); loading.destroy_indicator($('#admin_page_deactivated_users_loading_indicator')); $("#admin_deactivated_users_table").show(); $("#admin_users_table").show(); $("#admin_bots_table").show(); } exports.set_up = function () { loading.make_indicator($('#admin_page_users_loading_indicator'), {text: 'Loading...'}); loading.make_indicator($('#admin_page_bots_loading_indicator'), {text: 'Loading...'}); loading.make_indicator($('#admin_page_deactivated_users_loading_indicator'), {text: 'Loading...'}); $("#admin_deactivated_users_table").hide(); $("#admin_users_table").hide(); $("#admin_bots_table").hide(); // Populate users and bots tables channel.get({ url: '/json/users', idempotent: true, timeout: 10 * 1000, success: exports.on_load_success, error: failed_listing_users, }); }; function open_user_info_form_modal(person) { const html = render_user_info_form_modal({ user_id: person.user_id, email: person.email, full_name: people.get_full_name(person.user_id), is_admin: person.is_admin, is_guest: person.is_guest, is_member: !person.is_admin && !person.is_guest, is_bot: person.is_bot, }); const user_info_form_modal = $(html); const modal_container = $('#user-info-form-modal-container'); modal_container.empty().append(user_info_form_modal); overlays.open_modal('user-info-form-modal'); if (person.is_bot) { // Dynamically add the owner select control in order to // avoid performance issues in case of large number of users. const users_list = people.get_active_humans(); const owner_select = $(render_bot_owner_select({users_list: users_list})); owner_select.val(bot_data.get(person.user_id).owner || ""); modal_container.find(".edit_bot_owner_container").append(owner_select); } return user_info_form_modal; } exports.on_load_success = function (realm_people_data) { meta.loaded = true; populate_users(realm_people_data); const modal_elem = $("#deactivation_user_modal").expectOne(); $(".admin_user_table").on("click", ".deactivate", function (e) { // This click event must not get propagated to parent container otherwise the modal // will not show up because of a call to `close_active_modal` in `settings.js`. e.preventDefault(); e.stopPropagation(); const row = $(e.target).closest(".user_row"); const user_id = row.data('user-id'); const user = people.get_by_user_id(user_id); modal_elem.find(".email").text(user.email); modal_elem.find(".user_name").text(user.full_name); modal_elem.modal("show"); modal_elem.data('user-id', user_id); }); modal_elem.find('.do_deactivate_button').click(function () { const user_id = modal_elem.data('user-id'); const row = get_user_info_row(user_id); modal_elem.modal("hide"); const row_deactivate_button = row.find("button.deactivate"); row_deactivate_button.prop("disabled", true).text(i18n.t("Working…")); const opts = { success_continuation: function () { update_view_on_deactivate(row); }, error_continuation: function () { row_deactivate_button.text(i18n.t("Deactivate")); }, }; const status = get_status_field(); const url = '/json/users/' + encodeURIComponent(user_id); settings_ui.do_settings_change(channel.del, url, {}, status, opts); }); $(".admin_bot_table").on("click", ".deactivate", function (e) { e.preventDefault(); e.stopPropagation(); const button_elem = $(e.target); const row = button_elem.closest(".user_row"); const bot_id = parseInt(row.attr("data-user-id"), 10); const url = '/json/bots/' + encodeURIComponent(bot_id); const opts = { success_continuation: function () { update_view_on_deactivate(row); }, error_continuation: function (xhr) { ui_report.generic_row_button_error(xhr, button_elem); }, }; const status = get_status_field(); settings_ui.do_settings_change(channel.del, url, {}, status, opts); }); $(".admin_user_table, .admin_bot_table").on("click", ".reactivate", function (e) { e.preventDefault(); e.stopPropagation(); // Go up the tree until we find the user row, then grab the email element const button_elem = $(e.target); const row = button_elem.closest(".user_row"); const user_id = parseInt(row.attr("data-user-id"), 10); const url = '/json/users/' + encodeURIComponent(user_id) + "/reactivate"; const data = {}; const status = get_status_field(); const opts = { success_continuation: function () { update_view_on_reactivate(row); }, error_continuation: function (xhr) { ui_report.generic_row_button_error(xhr, button_elem); }, }; settings_ui.do_settings_change(channel.post, url, data, status, opts); }); $('.admin_bot_table').on('click', '.user_row .view_user_profile', function (e) { const owner_id = parseInt($(e.target).attr('data-owner-id'), 10); const owner = people.get_by_user_id(owner_id); popovers.show_user_profile(owner); e.stopPropagation(); e.preventDefault(); }); $(".admin_user_table, .admin_bot_table").on("click", ".open-user-form", function (e) { const user_id = parseInt($(e.currentTarget).attr("data-user-id"), 10); const person = people.get_by_user_id(user_id); if (!person) { return; } const user_info_form_modal = open_user_info_form_modal(person); const element = "#user-info-form-modal .custom-profile-field-form"; $(element).html(""); settings_account.append_custom_profile_fields(element, user_id); settings_account.initialize_custom_date_type_fields(element); const fields_user_pills = settings_account.initialize_custom_user_type_fields(element, user_id, true, false); let url; let data; const full_name = user_info_form_modal.find("input[name='full_name']"); user_info_form_modal.find('.submit_user_info_change').on("click", function (e) { e.preventDefault(); e.stopPropagation(); const user_role_select_value = user_info_form_modal.find('#user-role-select').val(); const admin_status = get_status_field(); if (person.is_bot) { url = "/json/bots/" + encodeURIComponent(user_id); data = { full_name: full_name.val(), }; const owner_select_value = user_info_form_modal.find('.bot_owner_select').val(); if (owner_select_value) { data.bot_owner_id = people.get_by_email(owner_select_value).user_id; } } else { const new_profile_data = []; $("#user-info-form-modal .custom_user_field_value").each(function () { // Remove duplicate datepicker input element generated flatpicker library if (!$(this).hasClass("form-control")) { new_profile_data.push({ id: parseInt($(this).closest(".custom_user_field").attr("data-field-id"), 10), value: $(this).val(), }); } }); // Append user type field values also for (const [field_id, field_pills] of fields_user_pills) { if (field_pills) { const user_ids = user_pill.get_user_ids(field_pills); new_profile_data.push({ id: field_id, value: user_ids, }); } } url = "/json/users/" + encodeURIComponent(user_id); data = { full_name: JSON.stringify(full_name.val()), is_admin: JSON.stringify(user_role_select_value === 'admin'), is_guest: JSON.stringify(user_role_select_value === 'guest'), profile_data: JSON.stringify(new_profile_data), }; } settings_ui.do_settings_change(channel.patch, url, data, admin_status); overlays.close_modal('user-info-form-modal'); }); }); }; window.settings_users = exports;