Added/Updated admin dashboard tables

• Added:
- New Account filtering
- PreviewCard viewing/sorting/filtering deleting (todo)
- DeletePreviewCardWorker, Service
- Status viewing/sorting/filtering deleting
- ChatMessage viewing/sorting/filtering deleting (todo)
- Account > Follows view

• Updated:
- LinkBlock to sort alphabetically
- Groups to be under "Moderation" instead of "Admin" in navigation.rb
- Status in admin to have group name/link
- Reports reset button
- Group filtering/sorting
- LinkBlock filtering/sorting
- Account now has bio and few more data points in dashboard
This commit is contained in:
mgabdev 2021-01-19 01:25:25 -05:00
parent 24fa2d3a74
commit 51fa8f2eb4
39 changed files with 697 additions and 108 deletions

@ -0,0 +1,76 @@
# frozen_string_literal: true
module Admin
class AccountStatusesController < BaseController
helper_method :current_params
before_action :set_account
PER_PAGE = 20
def index
authorize :status, :index?
@statuses = @account.statuses
if params[:media]
account_media_status_ids = @account.media_attachments.attached.reorder(nil).select(:status_id).distinct
@statuses.merge!(Status.where(id: account_media_status_ids))
end
@statuses = @statuses.preload(:media_attachments, :mentions).page(params[:page]).per(PER_PAGE)
@form = Form::StatusBatch.new
end
def show
authorize :status, :index?
@statuses = @account.statuses.where(id: params[:id])
authorize @statuses.first, :show?
@form = Form::StatusBatch.new
end
def create
authorize :status, :update?
@form = Form::StatusBatch.new(form_status_batch_params.merge(current_account: current_account, action: action_from_button))
flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save
redirect_to admin_account_account_statuses_path(@account.id, current_params)
rescue ActionController::ParameterMissing
flash[:alert] = I18n.t('admin.statuses.no_status_selected')
redirect_to admin_account_account_statuses_path(@account.id, current_params)
end
private
def form_status_batch_params
params.require(:form_status_batch).permit(:action, status_ids: [])
end
def set_account
@account = Account.find(params[:account_id])
end
def current_params
page = (params[:page] || 1).to_i
{
media: params[:media],
page: page > 1 && page,
}.select { |_, value| value.present? }
end
def action_from_button
if params[:nsfw_on]
'nsfw_on'
elsif params[:nsfw_off]
'nsfw_off'
elsif params[:delete]
'delete'
end
end
end
end

@ -205,7 +205,10 @@ module Admin
:display_name,
:email,
:ip,
:staff
:staff,
:note,
:status_count_gte,
:sign_up_date_gte,
)
end

@ -2,22 +2,20 @@
module Admin
class ChatMessagesController < BaseController
before_action :set_account
PER_PAGE = 100
PER_PAGE = 50
def index
authorize :chat_message, :index?
@chat_messages = ChatMessage.where(from_account: @account).page(params[:page]).per(PER_PAGE)
@chat_messages = filtered_chat_messages.page(params[:page]).per(PER_PAGE)
@form = Form::ChatMessageBatch.new
end
def show
authorize :chat_message, :index?
@chat_messages = @account.chat_messages.where(id: params[:id])
authorize @chat_messages.first, :show?
@chat_message = ChatMessage.where(id: params[:id])
authorize @chat_message, :show?
@form = Form::ChatMessageBatch.new
end
@ -37,19 +35,18 @@ module Admin
private
def filtered_chat_messages
ChatMessageFilter.new(filter_params).results
end
def form_chat_message_batch_params
params.require(:form_chat_message_batch).permit(:action, chat_message_ids: [])
end
def set_account
@account = Account.find(params[:account_id])
end
def current_params
page = (params[:page] || 1).to_i
{
media: params[:media],
page: page > 1 && page,
}.select { |_, value| value.present? }
end
@ -59,5 +56,15 @@ module Admin
'delete'
end
end
def filter_params
params.permit(
:id,
:text,
:account_id,
:created_at_lte,
:created_at_gte
)
end
end
end

@ -0,0 +1,18 @@
# frozen_string_literal: true
module Admin
class FollowsController < BaseController
before_action :set_account
PER_PAGE = 40
def index
authorize :account, :index?
@follows = @account.following.local.recent.page(params[:page]).per(PER_PAGE)
end
def set_account
@account = Account.find(params[:account_id])
end
end
end

@ -4,7 +4,6 @@ module Admin
class GroupsController < BaseController
before_action :set_group, except: [:index]
before_action :set_accounts, only: [:show]
before_action :set_filter_params
def index
authorize :group, :index?
@ -47,10 +46,6 @@ module Admin
@mods = GroupAccount.where(group: @group, role: 'moderator')
end
def set_filter_params
@filter_params = filter_params.to_hash.symbolize_keys
end
def resource_params
params.require(:group).permit(
:title,
@ -66,16 +61,17 @@ module Admin
end
def filtered_groups
query = Group.order('is_featured DESC, member_count DESC')
if params[:title]
query = query.where("LOWER(title) LIKE LOWER(?)", "%#{params[:title]}%")
end
return query
GroupFilter.new(filter_params).results
end
def filter_params
params.permit(:sort,)
params.permit(
:title,
:description,
:id,
:member_count_gte,
:created_at_gte
)
end
end
end

@ -6,7 +6,7 @@ module Admin
def index
authorize :link_block, :index?
@link_blocks = LinkBlock.page(params[:page])
@link_blocks = filtered_link_blocks.alphabetical.page(params[:page])
end
def new
@ -36,10 +36,18 @@ module Admin
private
def filtered_link_blocks
LinkBlockFilter.new(filter_params).results
end
def set_link_block
@link_block = LinkBlock.find(params[:id])
end
def filter_params
params.permit(:link)
end
def resource_params
params.require(:link_block).permit(:link)
end

@ -0,0 +1,61 @@
# frozen_string_literal: true
module Admin
class PreviewCardsController < BaseController
before_action :set_preview_card, except: [:index]
def index
authorize :preview_card, :index?
@preview_cards = filtered_preview_cards.page(params[:page])
@form = Form::PreviewCardBatch.new
end
def show
authorize @preview_card, :show?
@form = Form::PreviewCardBatch.new
end
def create
authorize :preview_card, :update?
@form = Form::PreviewCardBatch.new(form_preview_card_batch_params.merge(current_account: current_account, action: action_from_button))
flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save
redirect_to admin_preview_cards_path(@account.id, current_params)
rescue ActionController::ParameterMissing
flash[:alert] = I18n.t('admin.statuses.no_status_selected')
redirect_to admin_preview_cards_path(@account.id, current_params)
end
private
def form_preview_card_batch_params
params.require(:form_preview_card_batch).permit(:action, preview_card_ids: [])
end
def filtered_preview_cards
PreviewCardFilter.new(filter_params).results.order(id: :desc)
end
def filter_params
params.permit(
:title,
:description,
:url,
)
end
def set_preview_card
@preview_card = PreviewCard.find(params[:id])
end
def action_from_button
if params[:delete]
'delete'
end
end
end
end

@ -4,21 +4,12 @@ module Admin
class StatusesController < BaseController
helper_method :current_params
before_action :set_account
PER_PAGE = 20
def index
authorize :status, :index?
@statuses = @account.statuses.where(visibility: [:public, :unlisted])
if params[:media]
account_media_status_ids = @account.media_attachments.attached.reorder(nil).select(:status_id).distinct
@statuses.merge!(Status.where(id: account_media_status_ids))
end
@statuses = @statuses.preload(:media_attachments, :mentions).page(params[:page]).per(PER_PAGE)
@statuses = filtered_statuses.preload(:media_attachments, :mentions).page(params[:page]).per(PER_PAGE)
@form = Form::StatusBatch.new
end
@ -37,11 +28,11 @@ module Admin
@form = Form::StatusBatch.new(form_status_batch_params.merge(current_account: current_account, action: action_from_button))
flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save
redirect_to admin_account_statuses_path(@account.id, current_params)
redirect_to admin_statuses_path(current_params)
rescue ActionController::ParameterMissing
flash[:alert] = I18n.t('admin.statuses.no_status_selected')
redirect_to admin_account_statuses_path(@account.id, current_params)
redirect_to admin_statuses_path(current_params)
end
private
@ -50,8 +41,8 @@ module Admin
params.require(:form_status_batch).permit(:action, status_ids: [])
end
def set_account
@account = Account.find(params[:account_id])
def filtered_statuses
StatusFilter.new(nil, nil, nil, filter_params).results
end
def current_params
@ -63,6 +54,18 @@ module Admin
}.select { |_, value| value.present? }
end
def filter_params
params.permit(
:text,
:id,
:account_id,
:group_id,
:preview_card_id,
:created_at_lte,
:created_at_gte
)
end
def action_from_button
if params[:nsfw_on]
'nsfw_on'
@ -72,5 +75,6 @@ module Admin
'delete'
end
end
end
end

@ -1,7 +1,7 @@
# frozen_string_literal: true
module Admin::FilterHelper
ACCOUNT_FILTERS = %i(local remote by_domain active pending silenced suspended username display_name email ip staff).freeze
ACCOUNT_FILTERS = %i(local remote by_domain active pending silenced suspended username display_name email ip note staff).freeze
REPORT_FILTERS = %i(resolved account_id target_account_id).freeze
INVITE_FILTER = %i(available expired).freeze
CUSTOM_EMOJI_FILTERS = %i(local remote by_domain shortcode).freeze

@ -1,12 +1,13 @@
# frozen_string_literal: true
class StatusFilter
attr_reader :status, :account
attr_reader :status, :account, :params
def initialize(status, account, preloaded_relations = {})
def initialize(status, account, preloaded_relations = {}, params)
@status = status
@account = account
@preloaded_relations = preloaded_relations
@params = params
end
def filtered?
@ -14,6 +15,35 @@ class StatusFilter
blocked_by_policy? || (account_present? && filtered_status?) || silenced_account?
end
def results
scope = Status
params.each do |key, value|
scope = scope.merge scope_for(key, value) if !value.nil? && !value.empty?
end
scope
end
def scope_for(key, value)
case key.to_sym
when :text
Status.where("LOWER(text) LIKE LOWER(?)", "%#{value}%")
when :id
Status.where(id: value)
when :account_id
Status.where(account_id: value)
when :group_id
Status.where(group_id: value)
when :preview_card_id
Status.joins(:preview_cards).where("preview_cards.id = #{value.to_i}")
when :created_at_lte
Status.where("created_at <= ?", value)
when :created_at_gte
Status.where("created_at >= ?", value)
else
raise "Unknown filter: #{key}"
end
end
private
def account_present?

@ -51,6 +51,13 @@ class AccountFilter
valid_ip?(value) ? accounts_with_users.where('users.current_sign_in_ip <<= ?', value) : Account.none
when 'staff'
accounts_with_users.merge User.staff
when "note"
Account.where("LOWER(note) LIKE LOWER(?)", "%#{value}%")
when "status_count_gte"
# : todo :
Account.joins(:account_stat)
when "sign_up_date_gte"
Account.where("created_at >= ?", value)
else
raise "Unknown filter: #{key}"
end

@ -0,0 +1,34 @@
# frozen_string_literal: true
class ChatMessageFilter
attr_reader :params
def initialize(params)
@params = params
end
def results
scope = ChatMessage
params.each do |key, value|
scope = scope.merge scope_for(key, value) if !value.nil? && !value.empty?
end
scope
end
def scope_for(key, value)
case key.to_sym
when :text
ChatMessage.where("LOWER(text) LIKE LOWER(?)", "%#{value}%")
when :id
ChatMessage.where(id: value)
when :account_id
ChatMessage.where(from_account_id: value)
when :created_at_lte
ChatMessage.where("created_at <= ?", value)
when :created_at_gte
ChatMessage.where("created_at >= ?", value)
else
raise "Unknown filter: #{key}"
end
end
end

@ -0,0 +1,26 @@
# frozen_string_literal: true
class Form::PreviewCardBatch
include ActiveModel::Model
include AccountableConcern
attr_accessor :preview_card_ids, :action, :current_account
def save
case action
when 'delete'
delete_preview_cards
end
end
private
def delete_preview_cards
PreviewCard.where(id: preview_card_ids).reorder(nil).find_each do |preview_card|
DeletePreviewCardWorker.perform_async(preview_card.id)
log_action :destroy, preview_card
end
true
end
end

@ -0,0 +1,34 @@
# frozen_string_literal: true
class GroupFilter
attr_reader :params
def initialize(params)
@params = params
end
def results
scope = Group
params.each do |key, value|
scope = scope.merge scope_for(key, value) if !value.nil? && !value.empty?
end
scope
end
def scope_for(key, value)
case key.to_sym
when :id
Group.where(id: value)
when :title
Group.where("LOWER(title) LIKE LOWER(?)", "%#{value}%")
when :description
Group.where("LOWER(description) LIKE LOWER(?)", "%#{value}%")
when :member_count_gte
Group.where("member_count >= ?", value)
when :created_at_gte
Group.where("created_at >= ?", value)
else
raise "Unknown filter: #{key}"
end
end
end

@ -14,6 +14,8 @@ class LinkBlock < ApplicationRecord
validates :link, presence: true, uniqueness: true
scope :alphabetical, -> { reorder(link: :asc) }
def self.block?(text)
return false if text.nil?
return false if text.length < 1

@ -0,0 +1,26 @@
# frozen_string_literal: true
class LinkBlockFilter
attr_reader :params
def initialize(params)
@params = params
end
def results
scope = LinkBlock
params.each do |key, value|
scope = scope.merge scope_for(key, value)
end
scope
end
def scope_for(key, value)
case key.to_sym
when :link
LinkBlock.where("LOWER(link) LIKE LOWER(?)", "%#{value}%")
else
raise "Unknown filter: #{key}"
end
end
end

@ -0,0 +1,30 @@
# frozen_string_literal: true
class PreviewCardFilter
attr_reader :params
def initialize(params)
@params = params
end
def results
scope = PreviewCard
params.each do |key, value|
scope = scope.merge scope_for(key, value)
end
scope
end
def scope_for(key, value)
case key.to_sym
when :title
PreviewCard.where("LOWER(title) LIKE LOWER(?)", "%#{value}%")
when :description
PreviewCard.where("LOWER(description) LIKE LOWER(?)", "%#{value}%")
when :url
PreviewCard.where("LOWER(url) LIKE LOWER(?)", "%#{value}%")
else
raise "Unknown filter: #{key}"
end
end
end

@ -0,0 +1,15 @@
# frozen_string_literal: true
class PreviewCardPolicy < ApplicationPolicy
def destroy?
staff?
end
def index?
staff?
end
def show?
staff?
end
end

@ -0,0 +1,23 @@
# frozen_string_literal: true
class DeletePreviewCardService < BaseService
include Redisable
def call(preview_card)
return if preview_card.nil?
@preview_card = preview_card
RedisLock.acquire(lock_options) do |lock|
if lock.acquired?
@preview_card.destroy!
else
raise GabSocial::RaceConditionError
end
end
end
def lock_options
{ redis: Redis.current, key: "distribute_preview_card:#{@preview_card.id}" }
end
end

@ -0,0 +1,37 @@
- content_for :header_tags do
= javascript_pack_tag 'admin', integrity: true, async: true, crossorigin: 'anonymous'
- content_for :page_title do
= t('admin.statuses.title')
\-
= "@#{@account.acct}"
.filters
.filter-subset
%strong= t('admin.statuses.media.title')
%ul
%li= link_to t('admin.statuses.no_media'), admin_account_account_statuses_path(@account.id, current_params.merge(media: nil)), class: !params[:media] && 'selected'
%li= link_to t('admin.statuses.with_media'), admin_account_account_statuses_path(@account.id, current_params.merge(media: true)), class: params[:media] && 'selected'
.back-link{ style: 'flex: 1 1 auto; text-align: right' }
= link_to admin_account_path(@account.id) do
= fa_icon 'chevron-left fw'
= t('admin.statuses.back_to_account')
%hr.spacer/
= form_for(@form, url: admin_account_account_statuses_path(@account.id)) do |f|
= hidden_field_tag :page, params[:page]
= hidden_field_tag :media, params[:media]
.batch-table
.batch-table__toolbar
%label.batch-table__toolbar__select.batch-checkbox-all
= check_box_tag :batch_checkbox_all, nil, false
.batch-table__toolbar__actions
= f.button safe_join([fa_icon('eye-slash'), t('admin.statuses.batch.nsfw_on')]), name: :nsfw_on, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
= f.button safe_join([fa_icon('eye'), t('admin.statuses.batch.nsfw_off')]), name: :nsfw_off, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
= f.button safe_join([fa_icon('trash'), t('admin.statuses.batch.delete')]), name: :delete, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
.batch-table__body
= render partial: 'admin/reports/status', collection: @statuses, locals: { f: f }
= paginate @statuses

@ -20,11 +20,17 @@
- if params[key].present?
= hidden_field_tag key, params[key]
- %i(username by_domain display_name email ip).each do |key|
- %i(username by_domain display_name email ip note).each do |key|
- unless key == :by_domain && params[:remote].blank?
.input.string.optional
= text_field_tag key, params[key], class: 'string optional', placeholder: I18n.t("admin.accounts.#{key}")
.input.string.optional
= text_field_tag :status_count_gte, params[:status_count_gte], class: 'string optional', placeholder: "Status Count >="
.input.string.optional
= text_field_tag :sign_up_date_gte, params[:sign_up_date_gte], class: 'string optional', placeholder: "Sign up Date >= (MM-DD-YYYY)"
.actions
%button= t('admin.accounts.search')
= link_to t('admin.accounts.reset'), admin_accounts_path, class: 'button negative'

@ -5,25 +5,13 @@
.dashboard__counters{ style: 'margin-top: 10px' }
%div
= link_to admin_account_statuses_path(@account.id) do
= link_to admin_account_account_statuses_path(@account.id) do
.dashboard__counters__num= number_with_delimiter @account.statuses_count
.dashboard__counters__label= t 'admin.accounts.statuses'
%div
= link_to admin_account_statuses_path(@account.id, { media: true }) do
= link_to admin_account_account_statuses_path(@account.id, { media: true }) do
.dashboard__counters__num= number_to_human_size @account.media_attachments.sum('file_file_size')
.dashboard__counters__label= t 'admin.accounts.media_attachments'
%div
= link_to admin_account_followers_path(@account.id) do
.dashboard__counters__num= number_with_delimiter @account.local_followers_count
.dashboard__counters__label= t 'admin.accounts.followers'
%div
= link_to admin_reports_path(account_id: @account.id) do
.dashboard__counters__num= number_with_delimiter @account.reports.count
.dashboard__counters__label= t '.created_reports'
%div
= link_to admin_reports_path(target_account_id: @account.id) do
.dashboard__counters__num= number_with_delimiter @account.targeted_reports.count
.dashboard__counters__label= t '.targeted_reports'
%div
%div
.dashboard__counters__text
@ -42,12 +30,36 @@
- else
%span.neutral= t('admin.accounts.no_limits_imposed')
.dashboard__counters__label= t 'admin.accounts.login_status'
%div
= link_to admin_account_followers_path(@account.id) do
.dashboard__counters__num= number_with_delimiter @account.local_followers_count
.dashboard__counters__label= t 'admin.accounts.followers'
%div
= link_to admin_account_follows_path(@account.id) do
.dashboard__counters__num= number_with_delimiter @account.local_following_count
.dashboard__counters__label= t 'admin.accounts.following'
%div
%a
.dashboard__counters__num= number_with_delimiter @account.blocked_by.count
.dashboard__counters__label Blocked By
%div
= link_to admin_reports_path(account_id: @account.id) do
.dashboard__counters__num= number_with_delimiter @account.reports.count
.dashboard__counters__label= t '.created_reports'
%div
= link_to admin_reports_path(target_account_id: @account.id) do
.dashboard__counters__num= number_with_delimiter @account.targeted_reports.count
.dashboard__counters__label= t '.targeted_reports'
%div
= link_to admin_account_joined_groups_path(@account.id) do
.dashboard__counters__num= number_with_delimiter @account.groups.count
.dashboard__counters__label Joined Groups
%div
= link_to admin_account_chat_conversation_accounts_path(@account.id) do
= link_to admin_account_joined_groups_path(@account.id) do
.dashboard__counters__num= number_with_delimiter @account.group_removed_accounts.count
.dashboard__counters__label Removed by Groups
%div
%a{href: "/admin/chat_messages?account_id=#{@account.id}"}
.dashboard__counters__num= number_with_delimiter @account.chat_conversation_accounts_count
.dashboard__counters__label Chat Conversations
%div

@ -2,19 +2,26 @@
= javascript_pack_tag 'admin', integrity: true, async: true, crossorigin: 'anonymous'
- content_for :page_title do
%span Chat Messages
\-
= "@#{@account.acct}"
= "Chat Messages"
.filters
.back-link{ style: 'flex: 1 1 auto; text-align: right' }
= link_to admin_account_path(@account.id) do
= fa_icon 'chevron-left fw'
%span Back to account
= form_tag admin_chat_messages_url, method: 'GET', class: 'simple_form' do
.fields-group
.input.string.optional
= text_field_tag :text, params[:text], class: 'string optional', placeholder: "Text contains"
.input.string.optional
= text_field_tag :id, params[:id], class: 'string optional', placeholder: "Id"
.input.string.optional
= text_field_tag :account_id, params[:account_id], class: 'string optional', placeholder: "Account Id"
.input.string.optional
= text_field_tag :created_at_gte, params[:created_at_gte], class: 'string optional', placeholder: "Created >= (MM-DD-YYYY)"
.input.string.optional
= text_field_tag :created_at_lte, params[:created_at_lte], class: 'string optional', placeholder: "Created <= (MM-DD-YYYY)"
.actions
%button= t('admin.accounts.search')
= link_to t('admin.accounts.reset'), admin_chat_messages_path, class: 'button negative'
%hr.spacer/
= form_for(@form, url: admin_account_chat_messages_path(@account.id)) do |f|
= form_for(@form, url: admin_chat_messages_path('')) do |f|
= hidden_field_tag :page, params[:page]
.batch-table

@ -0,0 +1,28 @@
- content_for :page_title do
= "#{@account.acct} is Following..."
.filters
.filter-subset
%strong= t('admin.accounts.location.title')
%ul
%li= link_to t('admin.accounts.location.local'), admin_account_follows_path(@account.id), class: 'selected'
.back-link{ style: 'flex: 1 1 auto; text-align: right' }
= link_to admin_account_path(@account.id) do
= fa_icon 'chevron-left fw'
= t('admin.followers.back_to_account')
%hr.spacer/
.table-wrapper
%table.table
%thead
%tr
%th= t('admin.accounts.username')
%th= t('admin.accounts.role')
%th= t('admin.accounts.most_recent_ip')
%th= t('admin.accounts.most_recent_activity')
%th
%tbody
= render partial: 'admin/accounts/account', collection: @follows
= paginate @follows

@ -4,8 +4,15 @@
= form_tag admin_groups_url, method: 'GET', class: 'simple_form' do
.fields-group
.input.string.optional
= text_field_tag :title, params[:title], class: 'string optional', placeholder: I18n.t("admin.groups.name")
= text_field_tag :id, params[:id], class: 'string optional', placeholder: "Id"
.input.string.optional
= text_field_tag :title, params[:title], class: 'string optional', placeholder: "Title"
.input.string.optional
= text_field_tag :description, params[:description], class: 'string optional', placeholder: "Description"
.input.string.optional
= text_field_tag :member_count_gte, params[:member_count_gte], class: 'string optional', placeholder: "Member Count >="
.input.string.optional
= text_field_tag :created_at_gte, params[:created_at_gte], class: 'string optional', placeholder: "Created >= (MM-DD-YYYY)"
.actions
%button= t('admin.accounts.search')
= link_to t('admin.accounts.reset'), admin_groups_path, class: 'button negative'

@ -15,7 +15,7 @@
.dashboard__counters{ style: 'margin-top: 10px' }
%div
%div
%a{href: "/admin/statuses?group_id=#{@group.id}"}
.dashboard__counters__num= number_with_delimiter Status.where(group:@group).count
.dashboard__counters__label Status Count
%div
@ -30,6 +30,11 @@
%div
.dashboard__counters__num= number_with_delimiter @group.join_requests.count
.dashboard__counters__label Member Requests Count
%div
%div
.dashboard__counters__num
%time.formatted{ datetime: @group.created_at.iso8601, title: l(@group.created_at) }= l(@group.created_at)
.dashboard__counters__label Created
= simple_form_for(@group, url: admin_group_path(@group.id), html: { method: :put }) do |f|
= render 'shared/error_messages', object: @group

@ -1,6 +1,15 @@
- content_for :page_title do
= t('admin.link_blocks.title')
= form_tag admin_link_blocks_url, method: 'GET', class: 'simple_form' do
.fields-group
.input.string.optional
= text_field_tag :link, params[:link], class: 'string optional', placeholder: "Link"
.actions
%button= t('admin.accounts.search')
= link_to t('admin.accounts.reset'), admin_link_blocks_path, class: 'button negative'
.table-wrapper
%table.table
%thead

@ -0,0 +1,14 @@
.batch-table__row
%label.batch-table__row__select.batch-checkbox
= f.check_box :preview_card_ids, { multiple: true, include_hidden: false }, preview_card.id
.batch-table__row__content
%div
%div{style: "display:flex;flex-direction:row;"}
- if !preview_card.image.nil?
%div{style: "display:block;padding-right:10px;"}
%img{src: preview_card.image, style: "display:block;width:80px;height:60px;"}
%div
%p{style: "color:#fff;font-size:14px;font-weight:600;margin-bottom:5px;"}= preview_card.title
%p{style: "color:#bbb;font-size:12px;font-weight:400;margin-bottom:5px;"}= preview_card.description
%p{style: "color:#21D07B;font-size:12px;font-weight:400;"}= preview_card.url
%a{href: "/admin/statuses?preview_card_id=#{preview_card.id}", style: "display:block;padding:6px;background-color:#666;color:#000;margin-top:6px;font-size:13px;width:90px;line-height:20px;text-decoration:none;text-align:center;border-radius:8px;"} View Statuses

@ -0,0 +1,32 @@
- content_for :page_title do
= "Preview Cards"
= form_tag admin_preview_cards_url, method: 'GET', class: 'simple_form' do
.fields-group
.input.string.optional
= text_field_tag :title, params[:title], class: 'string optional', placeholder: "Title"
.input.string.optional
= text_field_tag :description, params[:description], class: 'string optional', placeholder: "Description"
.input.string.optional
= text_field_tag :url, params[:url], class: 'string optional', placeholder: "Url"
.actions
%button= t('admin.accounts.search')
= link_to t('admin.accounts.reset'), admin_preview_cards_path, class: 'button negative'
= form_for(@form, url: admin_preview_cards_path()) do |f|
= hidden_field_tag :page, params[:page]
= hidden_field_tag :media, params[:media]
.batch-table
.batch-table__toolbar
%label.batch-table__toolbar__select.batch-checkbox-all
= check_box_tag :batch_checkbox_all, nil, false
.batch-table__toolbar__actions
= f.button safe_join([fa_icon('trash'), "Delete"]), name: :delete, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
.batch-table__body
= render partial: 'admin/preview_cards/preview_card', collection: @preview_cards, locals: { f: f }
= paginate @preview_cards

@ -32,3 +32,6 @@
·
= fa_icon('eye-slash fw')
= t('stream_entries.sensitive_content')
- if !status.group_id.nil?
·
%a{href: "/groups/#{status.group_id}", style: "color:#757575;text-decoration:none;font-size:14px;"}= status.group.title

@ -10,13 +10,12 @@
= form_tag admin_reports_url, method: 'GET', class: 'simple_form' do
.fields-group
- %i(comment).each do |key|
.input.string.optional
= text_field_tag key, params[key], class: 'string optional', placeholder: "#{key}"
.input.string.optional
= text_field_tag :comment, params[:comment], class: 'string optional', placeholder: "Comment"
.actions
%button= t('admin.accounts.search')
= link_to t('admin.accounts.reset'), admin_accounts_path, class: 'button negative'
= link_to t('admin.accounts.reset'), admin_reports_path, class: 'button negative'
- @reports.group_by(&:target_account_id).each do |target_account_id, reports|
- target_account = reports.first.target_account

@ -2,24 +2,30 @@
= javascript_pack_tag 'admin', integrity: true, async: true, crossorigin: 'anonymous'
- content_for :page_title do
= t('admin.statuses.title')
\-
= "@#{@account.acct}"
= "All Statuses"
.filters
.filter-subset
%strong= t('admin.statuses.media.title')
%ul
%li= link_to t('admin.statuses.no_media'), admin_account_statuses_path(@account.id, current_params.merge(media: nil)), class: !params[:media] && 'selected'
%li= link_to t('admin.statuses.with_media'), admin_account_statuses_path(@account.id, current_params.merge(media: true)), class: params[:media] && 'selected'
.back-link{ style: 'flex: 1 1 auto; text-align: right' }
= link_to admin_account_path(@account.id) do
= fa_icon 'chevron-left fw'
= t('admin.statuses.back_to_account')
= form_tag admin_statuses_url, method: 'GET', class: 'simple_form' do
.fields-group
.input.string.optional
= text_field_tag :text, params[:text], class: 'string optional', placeholder: "Text contains"
.input.string.optional
= text_field_tag :id, params[:id], class: 'string optional', placeholder: "Id"
.input.string.optional
= text_field_tag :account_id, params[:account_id], class: 'string optional', placeholder: "Account Id"
.input.string.optional
= text_field_tag :group_id, params[:group_id], class: 'string optional', placeholder: "Group Id"
.input.string.optional
= text_field_tag :preview_card_id, params[:preview_card_id], class: 'string optional', placeholder: "Preview Card Id"
.input.string.optional
= text_field_tag :created_at_gte, params[:created_at_gte], class: 'string optional', placeholder: "Created >= (MM-DD-YYYY)"
.input.string.optional
= text_field_tag :created_at_lte, params[:created_at_lte], class: 'string optional', placeholder: "Created <= (MM-DD-YYYY)"
.actions
%button= t('admin.accounts.search')
= link_to t('admin.accounts.reset'), admin_statuses_path, class: 'button negative'
%hr.spacer/
= form_for(@form, url: admin_account_statuses_path(@account.id)) do |f|
= form_for(@form, url: admin_statuses_path) do |f|
= hidden_field_tag :page, params[:page]
= hidden_field_tag :media, params[:media]

@ -4,14 +4,17 @@
= link_to account_url, target: '_blank', rel: 'noopener' do
.card__img
= image_tag account.header.url, alt: ''
.card__bar
.avatar
= image_tag account.avatar.url, alt: '', width: 48, height: 48, class: 'u-photo'
.card__bar{style: "display:flex;flex-direction:column;align-items:flex-start;"}
%div{style: "display:flex;flex-direction:row;"}
.avatar
= image_tag account.avatar.url, alt: '', width: 48, height: 48, class: 'u-photo'
.display-name
%span{id: "default_account_display_name", style: "display:none;"}= account.username
%bdi
%strong.emojify.p-name= display_name(account, custom_emojify: true)
%span
= acct(account)
= fa_icon('lock') if account.locked?
%div{style: "display:block;margin-top:10px;"}
%p{style: "display:block;font-size:14px;color:#999;"}= account.note
.display-name
%span{id: "default_account_display_name", style: "display:none;"}= account.username
%bdi
%strong.emojify.p-name= display_name(account, custom_emojify: true)
%span
= acct(account)
= fa_icon('lock') if account.locked?

@ -0,0 +1,12 @@
# frozen_string_literal: true
class DeletePreviewCardWorker
include Sidekiq::Worker
sidekiq_options unique: :until_executed
def perform(preview_card_id)
return if preview_card_id.nil?
DeletePreviewCardService.new.call(PreviewCard.find(preview_card_id))
end
end

@ -155,6 +155,7 @@ en:
moderation_notes: Moderation notes
most_recent_activity: Most recent activity
most_recent_ip: Most recent IP
note: Bio
no_account_selected: No accounts were changed as none were selected
no_limits_imposed: No limits imposed
not_subscribed: Not subscribed

@ -132,6 +132,7 @@ en_GB:
moderation_notes: Moderation notes
most_recent_activity: Most recent activity
most_recent_ip: Most recent IP
note: Bio
no_limits_imposed: No limits imposed
not_subscribed: Not subscribed
outbox_url: Outbox URL

@ -28,17 +28,20 @@ SimpleNavigation::Configuration.run do |navigation|
n.item :development, safe_join([fa_icon('code fw'), t('settings.development')]), settings_applications_url, if: -> { current_user.staff? }
n.item :moderation, safe_join([fa_icon('gavel fw'), t('moderation.title')]), admin_reports_url, if: proc { current_user.staff? } do |s|
s.item :accounts, safe_join([fa_icon('users fw'), t('admin.accounts.title')]), admin_accounts_url, highlights_on: %r{/admin/accounts|/admin/pending_accounts}
s.item :action_logs, safe_join([fa_icon('bars fw'), t('admin.action_logs.title')]), admin_action_logs_url
s.item :accounts, safe_join([fa_icon('users fw'), t('admin.accounts.title')]), admin_accounts_url, highlights_on: %r{/admin/accounts|/admin/pending_accounts|admin/flagged_as_spam}
s.item :statuses, safe_join([fa_icon('bars fw'), "Statuses"]), admin_statuses_url
s.item :groups, safe_join([fa_icon('smile-o fw'), t('admin.groups.title')]), admin_groups_url, highlights_on: %r{/admin/groups}
s.item :reports, safe_join([fa_icon('flag fw'), t('admin.reports.title')]), admin_reports_url, highlights_on: %r{/admin/reports}
s.item :chat_messages, safe_join([fa_icon('comments fw'), "Chat Messages"]), admin_chat_messages_url, if: -> { current_user.admin? }
s.item :preview_cards, safe_join([fa_icon('link fw'), "Preview Cards"]), admin_preview_cards_url
s.item :action_logs, safe_join([fa_icon('bars fw'), t('admin.action_logs.title')]), admin_action_logs_url
s.item :email_domain_blocks, safe_join([fa_icon('envelope fw'), t('admin.email_domain_blocks.title')]), admin_email_domain_blocks_url, highlights_on: %r{/admin/email_domain_blocks}, if: -> { current_user.admin? }
s.item :link_blocks, safe_join([fa_icon('link fw'), t('admin.link_blocks.title')]), admin_link_blocks_url, highlights_on: %r{/admin/link_blocks}, if: -> { current_user.admin? }
s.item :link_blocks, safe_join([fa_icon('link fw'), t('admin.link_blocks.title')]), admin_link_blocks_url
end
n.item :admin, safe_join([fa_icon('cogs fw'), t('admin.title')]), admin_dashboard_url, if: proc { current_user.staff? } do |s|
s.item :dashboard, safe_join([fa_icon('tachometer fw'), t('admin.dashboard.title')]), admin_dashboard_url
s.item :settings, safe_join([fa_icon('cogs fw'), t('admin.settings.title')]), edit_admin_settings_url, if: -> { current_user.admin? }, highlights_on: %r{/admin/settings}
s.item :groups, safe_join([fa_icon('smile-o fw'), t('admin.groups.title')]), admin_groups_url, highlights_on: %r{/admin/groups}
s.item :custom_emojis, safe_join([fa_icon('smile-o fw'), t('admin.custom_emojis.title')]), admin_custom_emojis_url, highlights_on: %r{/admin/custom_emojis}
s.item :sidekiq, safe_join([fa_icon('diamond fw'), 'Sidekiq']), sidekiq_url, link_html: { target: 'sidekiq' }, if: -> { current_user.admin? }
s.item :pghero, safe_join([fa_icon('database fw'), 'PgHero']), pghero_url, link_html: { target: 'pghero' }, if: -> { current_user.admin? }

@ -94,6 +94,9 @@ Rails.application.routes.draw do
resources :email_domain_blocks, only: [:index, :new, :create, :destroy]
resources :link_blocks, only: [:index, :new, :create, :destroy]
resources :statuses, only: [:index, :show, :create, :update, :destroy]
resources :preview_cards, only: [:index, :create, :destroy]
resources :chat_messages, only: [:index, :destroy]
resources :action_logs, only: [:index]
resources :warning_presets, except: [:new]
resource :settings, only: [:edit, :update]
@ -139,8 +142,9 @@ Rails.application.routes.draw do
resource :change_email, only: [:show, :update]
resource :reset, only: [:create]
resource :action, only: [:new, :create], controller: 'account_actions'
resources :statuses, only: [:index, :show, :create, :update, :destroy]
resources :account_statuses, only: [:index, :show, :create, :update, :destroy]
resources :followers, only: [:index]
resources :follows, only: [:index]
resources :joined_groups, only: [:index]
resources :chat_conversation_accounts, only: [:index]
resources :chat_messages, only: [:index, :show, :create, :update, :destroy]