Commit 13b716a4 authored by Florian Staudacher's avatar Florian Staudacher

allow admins to close user accounts from the backend

* thanks to @maxwell for the initial work on this

port admin pages to bootstrap
* improve user view on admin search page
* add 'close account' link to each user in the search results
* keep the same blue color for the admin menu

some refactoring of the routes and the admin code
* try to be more RESTful (possibly)
* use a 'UserSearch' model for search parameters and querying

add changelog entry
parent cc53e176
......@@ -10,7 +10,8 @@
* Fix self-XSS when renaming an aspect [#5048](https://github.com/diaspora/diaspora/pull/5048)
* Fix live updating when renaming an aspect [#5049](https://github.com/diaspora/diaspora/pull/5049)
## FeatureS
## Features
* Port admin pages to bootstrap, polish user search results, allow accounts to be closed from the backend [#5046](https://github.com/diaspora/diaspora/pull/5046)
# 0.4.0.1
......
@import 'colors';
/** ADMIN STYlES **/
body > div.container {
margin-top: 40px;
padding-top: 1em;
}
#admin_nav {
font-size: 1em;
border-bottom: 2px solid #777;
margin-bottom: 20px;
ul {
display: inline;
}
li {
font-size: 0.8em;
display: inline;
margin-right: 0.5em;
a { color: $blue; }
}
}
/** user search **/
.users {
li.user {
border-bottom: 1px solid $light-grey;
margin-bottom: .4em;
padding-bottom: .4em;
&:last-child { border: none; }
.avatar {
width: 50px;
height: 50px;
}
.actions li {
margin-top: .3em;
}
}
}
/** reported posts **/
@import 'report'
......@@ -503,7 +503,7 @@ input[type="search"]
.right
:margin
:top 10px
h4
:display inline
:margin
......@@ -1329,20 +1329,6 @@ a.toggle_selector
&:hover
:text-decoration none
#admin_nav
:font-size 1em
:border
:bottom 2px solid #777
:margin
:bottom 20px
ul
:display inline
li
:font-size 0.8em
:display inline
:margin
:right 0.5em
#grey_header
@include box-shadow(0,1px,1px,#eee)
:background
......@@ -1429,7 +1415,7 @@ a.toggle_selector
:margin-top 60px
.creation
:font-size 16px
#profile_photo_upload
#fileInfo
:margin-top 12px
......
// Calling this file bootstrap would cause an infinite recursion during asset compilation.
@import 'bootstrap';
@import 'bootstrap-responsive';
\ No newline at end of file
@import 'bootstrap-responsive';
// according to the docs, this is part of bootstrap 2.3.x
.text-left { text-align: left; }
.text-center { text-align: center; }
.text-right { text-align: right; }
class Admin::AdminController < ApplicationController
before_filter :authenticate_user!
before_filter :redirect_unless_admin
end
class Admin::UsersController < Admin::AdminController
def close_account
u = User.find(close_account_params)
u.close_account!
redirect_to user_search_path, notice: t('admins.user_search.account_closing_scheduled', name: u.username)
end
private
def close_account_params
params.require(:id)
end
end
class AdminsController < ApplicationController
before_filter :authenticate_user!
before_filter :redirect_unless_admin
class AdminsController < Admin::AdminController
use_bootstrap_for :user_search, :weekly_user_stats, :stats, :correlations
def user_search
params[:user] ||= {}
params[:user].delete_if {|key, value| value.blank? }
@users = User.joins(person: :profile).where(["profiles.birthday > ?", Date.today - 13.years]) if params[:under13]
@users = (@users || User).where(params[:user]) if params[:user].present?
if params[:admins_controller_user_search]
search_params = params.require(:admins_controller_user_search)
.permit(:username, :email, :guid, :under13)
@search = UserSearch.new(search_params)
@users = @search.perform
end
@search ||= UserSearch.new
@users ||= []
end
def remove_spammer
user = User.find(params[:user_id])
user.close_account!
redirect_to root_url, notice:"this account will be deleted in a few moments"
end
def admin_inviter
inviter = InvitationCode.default_inviter_or(current_user)
......@@ -101,4 +99,54 @@ class AdminsController < ApplicationController
DATA
)
end
# TODO action needed after rails4 update
class UserSearch
#include ActiveModel::Model # rails4
include ActiveModel::Conversion
include ActiveModel::Validations
include ActiveModel::MassAssignmentSecurity
attr_accessor :username, :email, :guid, :under13
validate :any_searchfield_present?
def initialize(attributes={})
assign_attributes(attributes)
yield(self) if block_given?
end
def assign_attributes(values, options={})
sanitize_for_mass_assignment(values, options[:as]).each do |k, v|
send("#{k}=", v)
end
end
# TODO remove this once ActiveModel is included
def persisted?
false
end
def any_searchfield_present?
if %w(username email guid under13).all? { |attr| self.send(attr).blank? }
errors.add :base, "no fields for search set"
end
end
def perform
#return User.none unless valid? # rails4
return [] unless valid?
users = User.arel_table
people = Person.arel_table
profiles = Profile.arel_table
res = User.joins(person: :profile)
res = res.where(users[:username].matches("%#{username}%")) unless username.blank?
res = res.where(users[:email].matches("%#{email}%")) unless email.blank?
res = res.where(people[:guid].matches("%#{guid}%")) unless guid.blank?
res = res.where(profiles[:birthday].gt(Date.today-13.years)) if under13 == '1'
res
end
end
end
......@@ -6,6 +6,8 @@ class ReportController < ApplicationController
before_filter :authenticate_user!
before_filter :redirect_unless_admin, :except => [:create]
use_bootstrap_for :index
def index
@reports = Report.where(reviewed: false).all
end
......
- content_for :head do
= stylesheet_link_tag :admin
#admin_nav
%h2
= t('.pages')
......@@ -8,4 +12,4 @@
%li= link_to t('.report'), report_index_path
%li= link_to t('.correlations'), correlations_path
%li= link_to t('.sidekiq_monitor'), sidekiq_path
%li.user.media
%div.pull-left
- if user.person
%span.media-object
= person_image_tag(user.person)
%div.media-body.row
%div.pull-right
%span.label
= t('.id')
= user.id
%span.label.label-info
= t('.guid')
= user.person.guid if user.person
%h4.media-heading
= user.person.name if user.person
%div.pull-right
%ul.unstyled.text-right.actions
%li= link_to t('admins.user_search.view_profile'), person_path(user.person), class: 'btn btn-mini'
%li= link_to t('admins.user_search.add_invites'), add_invites_path(user.invitation_code), class: 'btn btn-info btn-mini'
- unless user.person.closed_account
%li= link_to t('admins.user_search.close_account'), admin_close_account_path(user), method: :post, data: { confirm: t('admins.user_search.are_you_sure') }, class: 'btn btn-danger btn-mini'
%div.row
%div.span5
%dl.dl-horizontal
%dt= t('username')
%dd= user.username
%dt= t('.email')
%dd= user.email
%dt= t('.diaspora_handle')
%dd= user.person.diaspora_handle
%dt= t('.last_seen')
%dd= user.last_seen || t('.unknown')
-if user.invited_by.present?
%dt= t('.invite_token')
%dd= invite_code_url(user.invited_by.invitation_code)
%dt= t('.account_closed')
%dd
- if user.person.closed_account
%span.badge.badge-warning= t('.yes')
- else
%span.badge.badge-success= t('.no')
%dt= t('.nsfw')
%dd
- if user.person.profile.nsfw
%span.badge.badge-warning= t('.yes')
- else
%span.badge.badge-success= t('.no')
%h4= t('layouts.header.profile')
%dl.dl-horizontal
%dt= t('people.profile_sidebar.born')
%dd= user.person.profile.birthday
%dt= t('people.profile_sidebar.gender')
%dd= user.person.profile.gender
%dt= t('people.profile_sidebar.location')
%dd= user.person.profile.location
%dt= t('people.profile_sidebar.bio')
%dd= user.person.profile.bio
.span-24
%div
= render :partial => 'admins/admin_bar'
%br
%br
.span-24.last
%h1
= t('.correlations_count')
%ul
- @correlations_hash.keys.each do |k|
%li
= "#{k.to_s}, #{@correlations_hash[k]}"
%div.row
%div.span12
%h1
= t('.correlations_count')
%ul
- @correlations_hash.keys.each do |k|
%li
= "#{k.to_s}, #{@correlations_hash[k]}"
.span-24
%div
= render :partial => 'admins/admin_bar'
%br
%br
.span-24.last
%h1
= t('.usage_statistic')
%div{:style => "float:right;"}
= form_tag('/admins/stats', :method => 'get') do
%select{:name => 'range'}
%option{:value => 'daily', :selected => ('selected' if params[:range] == 'daily')}
= t('.daily')
%option{:value => 'week', :selected => ('selected' if params[:range] == 'week')}
= t('.week')
%option{:value => '2weeks', :selected => ('selected' if params[:range] == '2weeks')}
= t('.2weeks')
%option{:value => 'month', :selected => ('selected' if params[:range] == 'month')}
= t('.month')
%h1
= t('.usage_statistic')
= submit_tag t('.go')
%br
%h3
!= t('.display_results', :segment => @segment)
%br
%br
%br
%div.pull-right
= form_tag('/admins/stats', :method => 'get', class: 'form-inline') do
%select{:name => 'range'}
%option{:value => 'daily', :selected => ('selected' if params[:range] == 'daily')}
= t('.daily')
%option{:value => 'week', :selected => ('selected' if params[:range] == 'week')}
= t('.week')
%option{:value => '2weeks', :selected => ('selected' if params[:range] == '2weeks')}
= t('.2weeks')
%option{:value => 'month', :selected => ('selected' if params[:range] == 'month')}
= t('.month')
%hr
.clearfix
= submit_tag t('.go'), class: 'btn btn-primary'
.span-24.last
%h3
!= t('.display_results', :segment => @segment)
%div.row
- [:posts, :comments, :aspect_memberships, :users].each do |name|
- model = eval("@#{name.to_s}")
- if name == :aspect_memberships
......@@ -43,7 +34,7 @@
- if name == :users
- name = t('.users', :count => model[:yesterday])
.span-6{:class => ('last' if name == t('.users', :count => model[:yesterday]))}
.span3
%h2{:style => 'font-weight:bold;'}
= name.to_s
%h4
......@@ -51,17 +42,15 @@
%span.percent_change{:class => (model[:change] > 0 ? "green" : "red")}
= "(#{model[:change]}%)"
%br
%br
%br
%hr
%p{:style => "text-align:center;"}
!= t('.current_segment', :post_yest => @posts[:yesterday]/@user_count.to_f, :post_day => @posts[:day_before]/@user_count.to_f)
.span-24.last
%h3
= t('.50_most')
- @popular_tags.each do |name,count|
!= t('.tag_name', :name_tag => name, :count_tag => count)
%br
%div.row
%div.span12
%p.alert.alert-info.text-center
!= t('.current_segment', :post_yest => @posts[:yesterday]/@user_count.to_f, :post_day => @posts[:day_before]/@user_count.to_f)
%div.row
%div.span12
%h3= t('.50_most')
%ul
- @popular_tags.each do |name,count|
%li
!= t('.tag_name', :name_tag => name, :count_tag => count)
.span-24
%div
= render :partial => 'admins/admin_bar'
.span-24.prepend-4
%h3
%div.row
%div.user_search.span9
%h3= t('admins.admin_bar.user_search')
= form_for @search, url: {action: 'user_search'}, html: {method: :get, class: 'form-horizontal'} do |f|
%div.control-group
= f.label :username, t('username'), class: 'control-label'
%div.controls
= f.text_field :username
%div.control-group
= f.label :email, t('email'), class: 'control-label'
%div.controls
= f.text_field :email
%div.control-group
= f.label :guid, t('admins.user_entry.guid'), class: 'control-label'
%div.controls
= f.text_field :guid
%div.control-group
%div.controls
= f.label :under13 do
= f.check_box :under13
= t('.under_13')
= submit_tag t('admins.stats.go')
%div.more_invites.span3
%h3= t('shared.invitations.invites')
!= t('.you_currently', :count => current_user.invitation_code.count, :link => link_to(t(".add_invites"), add_invites_path(current_user.invitation_code)))
= form_tag 'admin_inviter', :method => :get do
= form_tag 'admin_inviter', method: :get do
= t('.email_to')
= text_field_tag 'identifier'
= submit_tag t('services.remote_friend.invite')
%div.row
%div.span12
%div.alert.alert-info.text-center= t('.users', :count => @users.count)
%h3
= t('admins.admin_bar.user_search')
= form_tag 'user_search', :method => :get do
= t('username')
= text_field_tag 'user[username]', params[:user][:username]
= t('email')
= text_field_tag 'user[email]', params[:user][:email]
= t('.under_13')
= check_box_tag 'under13', params[:under13]
= submit_tag t('admins.stats.go')
= t('.users', :count => @users.count)
%br
%br
- @users.each do |user|
= user.inspect
%br
- if user.person
= user.person.inspect
%br
- if user.person.profile
= user.person.profile.inspect
%br
= "invite token: #{invite_code_url(user.invited_by.invite_code)}" if user.invited_by.present?
= link_to t(".add_invites"), add_invites_path(user.invitation_code)
%br
%br
%br
%div.row
%div.users.span12
%ul.media-list
- @users.each do |user|
= render partial: 'user_entry', locals: { user: user }
.span-24
= render partial: 'admins/admin_bar'
%br
%br
.span-24.last
%h2
= t('.current_server', date: Time.now.to_date)
%div
= render :partial => 'admins/admin_bar'
%h2
= t('.current_server', date: Time.now.to_date)
= form_tag('/admins/weekly_user_stats', method: 'get') do
= select_tag(:week, options_for_select(@created_users_by_week.keys), selected: @selected_week)
= submit_tag t('admins.stats.go')
%div.pull-right
= form_tag('/admins/weekly_user_stats', method: 'get', class: 'form-inline') do
= select_tag(:week, options_for_select(@created_users_by_week.keys), selected: @selected_week)
= submit_tag t('admins.stats.go'), class: 'btn btn-primary'
= t('.amount_of', count: @counter)
%br
......
- content_for :container_content do
- if @css_framework == :bootstrap
- content_for :container_content do
= yield
- else
- content_for :container_content do
.span-24.last
= yield
= yield
= render template: "layouts/with_header_with_footer"
......@@ -90,7 +90,3 @@
%br
%br
- if current_user.admin? && person.owner.present?
= link_to 'Disable Account', remove_spammer_path(user_id:person.owner.id), method: :delete, data:{confirm:'Are you sure you want to disable this account? It will delete all data associated.'}
\ No newline at end of file
.span-24
%div
= render :partial => 'admins/admin_bar'
.span-24.last
%h1
= t('report.title')
%div#reports
- @reports.each do |r|
- username = User.find_by_id(r.user_id).username
%div.content
%span.text
= report_content(r.item_id, r.item_type)
%span
= raw t('report.reported_label', person: link_to(username, user_profile_path(username)))
%span
= t('report.reason_label', text: r.text)
%div.options
%span
= button_to t('report.review_link'), report_path(r.id, :type => r.item_type),
:class => "button",
method: :put
%span
= button_to t('report.delete_link'), report_path(r.id, :type => r.item_type),
:data => { :confirm => t('report.confirm_deletion') },
:class => "button delete",
method: :delete
%div.clear
%div.row
%div.span12
%h1
= t('report.title')
%div#reports
- @reports.each do |r|
- username = User.find_by_id(r.user_id).username