diff --git a/CHANGELOG.md b/CHANGELOG.md
index e8729a93b..f7505491b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@
- New media:fix-nonlocal-driver command. Fixes s3 media created with invalid FILESYSTEM_DRIVER=s3 configuration ([672cccd4](https://github.com/pixelfed/pixelfed/commit/672cccd4))
- New landing page design ([09c0032b](https://github.com/pixelfed/pixelfed/commit/09c0032b))
- Add cloud ip bans to BouncerService ([50ab2e20](https://github.com/pixelfed/pixelfed/commit/50ab2e20))
+- Redesigned Admin Dashboard Reports/Moderation ([c6cc6327](https://github.com/pixelfed/pixelfed/commit/c6cc6327))
### Updates
- Update ApiV1Controller, fix blocking remote accounts. Closes #4256 ([8e71e0c0](https://github.com/pixelfed/pixelfed/commit/8e71e0c0))
diff --git a/app/Http/Controllers/Admin/AdminReportController.php b/app/Http/Controllers/Admin/AdminReportController.php
index 59ae9dfe9..2ee8e02c5 100644
--- a/app/Http/Controllers/Admin/AdminReportController.php
+++ b/app/Http/Controllers/Admin/AdminReportController.php
@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Admin;
use Cache;
use Carbon\Carbon;
use Illuminate\Http\Request;
+use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;
use App\Services\AccountService;
use App\Services\StatusService;
@@ -24,6 +25,13 @@ use Illuminate\Validation\Rule;
use App\Services\StoryService;
use App\Services\ModLogService;
use App\Jobs\DeletePipeline\DeleteAccountPipeline;
+use App\Jobs\DeletePipeline\DeleteRemoteProfilePipeline;
+use App\Jobs\DeletePipeline\DeleteRemoteStatusPipeline;
+use App\Jobs\StatusPipeline\StatusDelete;
+use App\Http\Resources\AdminReport;
+use App\Http\Resources\AdminSpamReport;
+use App\Services\PublicTimelineService;
+use App\Services\NetworkTimelineService;
trait AdminReportController
{
@@ -74,6 +82,9 @@ trait AdminReportController
public function showReport(Request $request, $id)
{
$report = Report::with('status')->findOrFail($id);
+ if($request->has('ref') && $request->input('ref') == 'email') {
+ return redirect('/i/admin/reports?tab=report&id=' . $report->id);
+ }
return view('admin.reports.show', compact('report'));
}
@@ -200,6 +211,9 @@ trait AdminReportController
{
$appeal = AccountInterstitial::whereType('post.autospam')
->findOrFail($id);
+ if($request->has('ref') && $request->input('ref') == 'email') {
+ return redirect('/i/admin/reports?tab=autospam&id=' . $appeal->id);
+ }
$meta = json_decode($appeal->meta);
return view('admin.reports.show_spam', compact('appeal', 'meta'));
}
@@ -601,4 +615,588 @@ trait AdminReportController
Redis::del('email:manual-ignored');
return [200];
}
+
+ public function reportsStats(Request $request)
+ {
+ $stats = [
+ 'total' => Report::count(),
+ 'open' => Report::whereNull('admin_seen')->count(),
+ 'closed' => Report::whereNotNull('admin_seen')->count(),
+ 'autospam' => AccountInterstitial::whereType('post.autospam')->count(),
+ 'autospam_open' => AccountInterstitial::whereType('post.autospam')->whereNull(['appeal_handled_at'])->count(),
+ 'appeals' => AccountInterstitial::whereNotNull('appeal_requested_at')->whereNull('appeal_handled_at')->count(),
+ 'email_verification_requests' => Redis::scard('email:manual')
+ ];
+ return $stats;
+ }
+
+ public function reportsApiAll(Request $request)
+ {
+ $filter = $request->input('filter') == 'closed' ? 'closed' : 'open';
+
+ $reports = AdminReport::collection(
+ Report::orderBy('id','desc')
+ ->when($filter, function($q, $filter) {
+ return $filter == 'open' ?
+ $q->whereNull('admin_seen') :
+ $q->whereNotNull('admin_seen');
+ })
+ ->groupBy(['object_id', 'object_type'])
+ ->cursorPaginate(6)
+ ->withQueryString()
+ );
+
+ return $reports;
+ }
+
+ public function reportsApiGet(Request $request, $id)
+ {
+ $report = Report::findOrFail($id);
+ return new AdminReport($report);
+ }
+
+ public function reportsApiHandle(Request $request)
+ {
+ $this->validate($request, [
+ 'object_id' => 'required',
+ 'object_type' => 'required',
+ 'id' => 'required',
+ 'action' => 'required|in:ignore,nsfw,unlist,private,delete',
+ 'action_type' => 'required|in:post,profile'
+ ]);
+
+ $report = Report::whereObjectId($request->input('object_id'))->findOrFail($request->input('id'));
+
+ if($request->input('action_type') === 'profile') {
+ return $this->reportsHandleProfileAction($report, $request->input('action'));
+ } else if($request->input('action_type') === 'post') {
+ return $this->reportsHandleStatusAction($report, $request->input('action'));
+ }
+
+ return $report;
+ }
+
+ protected function reportsHandleProfileAction($report, $action)
+ {
+ switch($action) {
+ case 'ignore':
+ Report::whereObjectId($report->object_id)
+ ->whereObjectType($report->object_type)
+ ->update([
+ 'admin_seen' => now()
+ ]);
+ return [200];
+ break;
+
+ case 'nsfw':
+ if($report->object_type === 'App\Profile') {
+ $profile = Profile::find($report->object_id);
+ } else if($report->object_type === 'App\Status') {
+ $status = Status::find($report->object_id);
+ if(!$status) {
+ return [200];
+ }
+ $profile = Profile::find($status->profile_id);
+ }
+
+ if(!$profile) {
+ return;
+ }
+
+ abort_if($profile->user && $profile->user->is_admin, 400, 'Cannot moderate an admin account.');
+
+ $profile->cw = true;
+ $profile->save();
+
+ foreach(Status::whereProfileId($profile->id)->cursor() as $status) {
+ $status->is_nsfw = true;
+ $status->save();
+ StatusService::del($status->id);
+ PublicTimelineService::rem($status->id);
+ }
+
+ ModLogService::boot()
+ ->objectUid($profile->id)
+ ->objectId($profile->id)
+ ->objectType('App\Profile::class')
+ ->user(request()->user())
+ ->action('admin.user.moderate')
+ ->metadata([
+ 'action' => 'cw',
+ 'message' => 'Success!'
+ ])
+ ->accessLevel('admin')
+ ->save();
+
+ Report::whereObjectId($report->object_id)
+ ->whereObjectType($report->object_type)
+ ->update([
+ 'nsfw' => true,
+ 'admin_seen' => now()
+ ]);
+ return [200];
+ break;
+
+ case 'unlist':
+ if($report->object_type === 'App\Profile') {
+ $profile = Profile::find($report->object_id);
+ } else if($report->object_type === 'App\Status') {
+ $status = Status::find($report->object_id);
+ if(!$status) {
+ return [200];
+ }
+ $profile = Profile::find($status->profile_id);
+ }
+
+ if(!$profile) {
+ return;
+ }
+
+ abort_if($profile->user && $profile->user->is_admin, 400, 'Cannot moderate an admin account.');
+
+ $profile->unlisted = true;
+ $profile->save();
+
+ foreach(Status::whereProfileId($profile->id)->whereScope('public')->cursor() as $status) {
+ $status->scope = 'unlisted';
+ $status->visibility = 'unlisted';
+ $status->save();
+ StatusService::del($status->id);
+ PublicTimelineService::rem($status->id);
+ }
+
+ ModLogService::boot()
+ ->objectUid($profile->id)
+ ->objectId($profile->id)
+ ->objectType('App\Profile::class')
+ ->user(request()->user())
+ ->action('admin.user.moderate')
+ ->metadata([
+ 'action' => 'unlisted',
+ 'message' => 'Success!'
+ ])
+ ->accessLevel('admin')
+ ->save();
+
+ Report::whereObjectId($report->object_id)
+ ->whereObjectType($report->object_type)
+ ->update([
+ 'admin_seen' => now()
+ ]);
+ return [200];
+ break;
+
+ case 'private':
+ if($report->object_type === 'App\Profile') {
+ $profile = Profile::find($report->object_id);
+ } else if($report->object_type === 'App\Status') {
+ $status = Status::find($report->object_id);
+ if(!$status) {
+ return [200];
+ }
+ $profile = Profile::find($status->profile_id);
+ }
+
+ if(!$profile) {
+ return;
+ }
+
+ abort_if($profile->user && $profile->user->is_admin, 400, 'Cannot moderate an admin account.');
+
+ $profile->unlisted = true;
+ $profile->save();
+
+ foreach(Status::whereProfileId($profile->id)->cursor() as $status) {
+ $status->scope = 'private';
+ $status->visibility = 'private';
+ $status->save();
+ StatusService::del($status->id);
+ PublicTimelineService::rem($status->id);
+ }
+
+ ModLogService::boot()
+ ->objectUid($profile->id)
+ ->objectId($profile->id)
+ ->objectType('App\Profile::class')
+ ->user(request()->user())
+ ->action('admin.user.moderate')
+ ->metadata([
+ 'action' => 'private',
+ 'message' => 'Success!'
+ ])
+ ->accessLevel('admin')
+ ->save();
+
+ Report::whereObjectId($report->object_id)
+ ->whereObjectType($report->object_type)
+ ->update([
+ 'admin_seen' => now()
+ ]);
+ return [200];
+ break;
+
+ case 'delete':
+ if(config('pixelfed.account_deletion') == false) {
+ abort(404);
+ }
+
+ if($report->object_type === 'App\Profile') {
+ $profile = Profile::find($report->object_id);
+ } else if($report->object_type === 'App\Status') {
+ $status = Status::find($report->object_id);
+ if(!$status) {
+ return [200];
+ }
+ $profile = Profile::find($status->profile_id);
+ }
+
+ if(!$profile) {
+ return;
+ }
+
+ abort_if($profile->user && $profile->user->is_admin, 400, 'Cannot delete an admin account.');
+
+ $ts = now()->addMonth();
+
+ if($profile->user_id) {
+ $user = $profile->user;
+ abort_if($user->is_admin, 403, 'You cannot delete admin accounts.');
+ $user->status = 'delete';
+ $user->delete_after = $ts;
+ $user->save();
+ }
+
+ $profile->status = 'delete';
+ $profile->delete_after = $ts;
+ $profile->save();
+
+ ModLogService::boot()
+ ->objectUid($profile->id)
+ ->objectId($profile->id)
+ ->objectType('App\Profile::class')
+ ->user(request()->user())
+ ->action('admin.user.delete')
+ ->accessLevel('admin')
+ ->save();
+
+ Report::whereObjectId($report->object_id)
+ ->whereObjectType($report->object_type)
+ ->update([
+ 'admin_seen' => now()
+ ]);
+
+ if($profile->user_id) {
+ DB::table('oauth_access_tokens')->whereUserId($user->id)->delete();
+ DB::table('oauth_auth_codes')->whereUserId($user->id)->delete();
+ $user->email = $user->id;
+ $user->password = '';
+ $user->status = 'delete';
+ $user->save();
+ $profile->status = 'delete';
+ $profile->delete_after = now()->addMonth();
+ $profile->save();
+ AccountService::del($profile->id);
+ DeleteAccountPipeline::dispatch($user)->onQueue('high');
+ } else {
+ $profile->status = 'delete';
+ $profile->delete_after = now()->addMonth();
+ $profile->save();
+ AccountService::del($profile->id);
+ DeleteRemoteProfilePipeline::dispatch($profile)->onQueue('high');
+ }
+ return [200];
+ break;
+ }
+ }
+
+ protected function reportsHandleStatusAction($report, $action)
+ {
+ switch($action) {
+ case 'ignore':
+ Report::whereObjectId($report->object_id)
+ ->whereObjectType($report->object_type)
+ ->update([
+ 'admin_seen' => now()
+ ]);
+ return [200];
+ break;
+
+ case 'nsfw':
+ $status = Status::find($report->object_id);
+
+ if(!$status) {
+ return [200];
+ }
+
+ abort_if($status->profile->user && $status->profile->user->is_admin, 400, 'Cannot moderate an admin account post.');
+ $status->is_nsfw = true;
+ $status->save();
+ StatusService::del($status->id);
+
+ ModLogService::boot()
+ ->objectUid($status->profile_id)
+ ->objectId($status->profile_id)
+ ->objectType('App\Status::class')
+ ->user(request()->user())
+ ->action('admin.status.moderate')
+ ->metadata([
+ 'action' => 'cw',
+ 'message' => 'Success!'
+ ])
+ ->accessLevel('admin')
+ ->save();
+
+ Report::whereObjectId($report->object_id)
+ ->whereObjectType($report->object_type)
+ ->update([
+ 'nsfw' => true,
+ 'admin_seen' => now()
+ ]);
+ return [200];
+ break;
+
+ case 'private':
+ $status = Status::find($report->object_id);
+
+ if(!$status) {
+ return [200];
+ }
+
+ abort_if($status->profile->user && $status->profile->user->is_admin, 400, 'Cannot moderate an admin account post.');
+
+ $status->scope = 'private';
+ $status->visibility = 'private';
+ $status->save();
+ StatusService::del($status->id);
+ PublicTimelineService::rem($status->id);
+
+ ModLogService::boot()
+ ->objectUid($status->profile_id)
+ ->objectId($status->profile_id)
+ ->objectType('App\Status::class')
+ ->user(request()->user())
+ ->action('admin.status.moderate')
+ ->metadata([
+ 'action' => 'private',
+ 'message' => 'Success!'
+ ])
+ ->accessLevel('admin')
+ ->save();
+
+ Report::whereObjectId($report->object_id)
+ ->whereObjectType($report->object_type)
+ ->update([
+ 'admin_seen' => now()
+ ]);
+ return [200];
+ break;
+
+ case 'unlist':
+ $status = Status::find($report->object_id);
+
+ if(!$status) {
+ return [200];
+ }
+
+ abort_if($status->profile->user && $status->profile->user->is_admin, 400, 'Cannot moderate an admin account post.');
+
+ if($status->scope === 'public') {
+ $status->scope = 'unlisted';
+ $status->visibility = 'unlisted';
+ $status->save();
+ StatusService::del($status->id);
+ PublicTimelineService::rem($status->id);
+ }
+
+ ModLogService::boot()
+ ->objectUid($status->profile_id)
+ ->objectId($status->profile_id)
+ ->objectType('App\Status::class')
+ ->user(request()->user())
+ ->action('admin.status.moderate')
+ ->metadata([
+ 'action' => 'unlist',
+ 'message' => 'Success!'
+ ])
+ ->accessLevel('admin')
+ ->save();
+
+ Report::whereObjectId($report->object_id)
+ ->whereObjectType($report->object_type)
+ ->update([
+ 'admin_seen' => now()
+ ]);
+ return [200];
+ break;
+
+ case 'delete':
+ $status = Status::find($report->object_id);
+
+ if(!$status) {
+ return [200];
+ }
+
+ $profile = $status->profile;
+
+ abort_if($profile->user && $profile->user->is_admin, 400, 'Cannot delete an admin account post.');
+
+ StatusService::del($status->id);
+
+ if($profile->user_id != null && $profile->domain == null) {
+ PublicTimelineService::del($status->id);
+ StatusDelete::dispatch($status)->onQueue('high');
+ } else {
+ NetworkTimelineService::del($status->id);
+ DeleteRemoteStatusPipeline::dispatch($status)->onQueue('high');
+ }
+
+ Report::whereObjectId($report->object_id)
+ ->whereObjectType($report->object_type)
+ ->update([
+ 'admin_seen' => now()
+ ]);
+
+ return [200];
+ break;
+ }
+ }
+
+ public function reportsApiSpamAll(Request $request)
+ {
+ $tab = $request->input('tab', 'home');
+
+ $appeals = AdminSpamReport::collection(
+ AccountInterstitial::orderBy('id', 'desc')
+ ->whereType('post.autospam')
+ ->whereNull('appeal_handled_at')
+ ->cursorPaginate(6)
+ ->withQueryString()
+ );
+
+ return $appeals;
+ }
+
+ public function reportsApiSpamHandle(Request $request)
+ {
+ $this->validate($request, [
+ 'id' => 'required',
+ 'action' => 'required|in:mark-read,mark-not-spam,mark-all-read,mark-all-not-spam,delete-profile',
+ ]);
+
+ $action = $request->input('action');
+
+ abort_if(
+ $action === 'delete-profile' &&
+ !config('pixelfed.account_deletion'),
+ 404,
+ "Cannot delete profile, account_deletion is disabled.\n\n Set `ACCOUNT_DELETION=true` in .env and re-cache config."
+ );
+
+ $report = AccountInterstitial::with('user')
+ ->whereType('post.autospam')
+ ->whereNull('appeal_handled_at')
+ ->findOrFail($request->input('id'));
+
+ $this->reportsHandleSpamAction($report, $action);
+ Cache::forget('admin-dash:reports:spam-count');
+ Cache::forget('pf:bouncer_v0:exemption_by_pid:' . $report->user->profile_id);
+ Cache::forget('pf:bouncer_v0:recent_by_pid:' . $report->user->profile_id);
+ PublicTimelineService::warmCache(true, 400);
+ return [$action, $report];
+ }
+
+ public function reportsHandleSpamAction($appeal, $action)
+ {
+ $meta = json_decode($appeal->meta);
+
+ if($action == 'mark-read') {
+ $appeal->is_spam = true;
+ $appeal->appeal_handled_at = now();
+ $appeal->save();
+ }
+
+ if($action == 'mark-not-spam') {
+ $status = $appeal->status;
+ $status->is_nsfw = $meta->is_nsfw;
+ $status->scope = 'public';
+ $status->visibility = 'public';
+ $status->save();
+
+ $appeal->is_spam = false;
+ $appeal->appeal_handled_at = now();
+ $appeal->save();
+
+ StatusService::del($status->id);
+ }
+
+ if($action == 'mark-all-read') {
+ AccountInterstitial::whereType('post.autospam')
+ ->whereItemType('App\Status')
+ ->whereNull('appeal_handled_at')
+ ->whereUserId($appeal->user_id)
+ ->update([
+ 'appeal_handled_at' => now(),
+ 'is_spam' => true
+ ]);
+ }
+
+ if($action == 'mark-all-not-spam') {
+ AccountInterstitial::whereType('post.autospam')
+ ->whereItemType('App\Status')
+ ->whereUserId($appeal->user_id)
+ ->get()
+ ->each(function($report) use($meta) {
+ $report->is_spam = false;
+ $report->appeal_handled_at = now();
+ $report->save();
+ $status = Status::find($report->item_id);
+ if($status) {
+ $status->is_nsfw = $meta->is_nsfw;
+ $status->scope = 'public';
+ $status->visibility = 'public';
+ $status->save();
+ StatusService::del($status->id);
+ }
+ });
+ }
+
+ if($action == 'delete-profile') {
+ $user = User::findOrFail($appeal->user_id);
+ $profile = $user->profile;
+
+ if($user->is_admin == true) {
+ $mid = request()->user()->id;
+ abort_if($user->id < $mid, 403, 'You cannot delete an admin account.');
+ }
+
+ $ts = now()->addMonth();
+ $user->status = 'delete';
+ $profile->status = 'delete';
+ $user->delete_after = $ts;
+ $profile->delete_after = $ts;
+ $user->save();
+ $profile->save();
+
+ $appeal->appeal_handled_at = now();
+ $appeal->save();
+
+ ModLogService::boot()
+ ->objectUid($user->id)
+ ->objectId($user->id)
+ ->objectType('App\User::class')
+ ->user(request()->user())
+ ->action('admin.user.delete')
+ ->accessLevel('admin')
+ ->save();
+
+ Cache::forget('profiles:private');
+ DeleteAccountPipeline::dispatch($user);
+ }
+ }
+
+ public function reportsApiSpamGet(Request $request, $id)
+ {
+ $report = AccountInterstitial::findOrFail($id);
+ return new AdminSpamReport($report);
+ }
}
diff --git a/app/Http/Resources/AdminReport.php b/app/Http/Resources/AdminReport.php
new file mode 100644
index 000000000..c541e58cd
--- /dev/null
+++ b/app/Http/Resources/AdminReport.php
@@ -0,0 +1,38 @@
+
+ */
+ public function toArray(Request $request): array
+ {
+ $res = [
+ 'id' => $this->id,
+ 'reporter' => AccountService::get($this->profile_id, true),
+ 'type' => $this->type,
+ 'object_id' => (string) $this->object_id,
+ 'object_type' => $this->object_type,
+ 'reported' => AccountService::get($this->reported_profile_id, true),
+ 'status' => null,
+ 'reporter_message' => $this->message,
+ 'admin_seen_at' => $this->admin_seen,
+ 'created_at' => $this->created_at,
+ ];
+
+ if($this->object_id && $this->object_type === 'App\Status') {
+ $res['status'] = StatusService::get($this->object_id, false);
+ }
+
+ return $res;
+ }
+}
diff --git a/app/Http/Resources/AdminSpamReport.php b/app/Http/Resources/AdminSpamReport.php
new file mode 100644
index 000000000..6e2badd0f
--- /dev/null
+++ b/app/Http/Resources/AdminSpamReport.php
@@ -0,0 +1,33 @@
+
+ */
+ public function toArray(Request $request): array
+ {
+ $res = [
+ 'id' => $this->id,
+ 'type' => $this->type,
+ 'status' => null,
+ 'read_at' => $this->read_at,
+ 'created_at' => $this->created_at,
+ ];
+
+ if($this->item_id && $this->item_type === 'App\Status') {
+ $res['status'] = StatusService::get($this->item_id, false);
+ }
+
+ return $res;
+ }
+}
diff --git a/public/js/admin.js b/public/js/admin.js
index c51e4892d..b2efdf1dd 100644
Binary files a/public/js/admin.js and b/public/js/admin.js differ
diff --git a/public/mix-manifest.json b/public/mix-manifest.json
index fdef32cdc..c1d72cb20 100644
Binary files a/public/mix-manifest.json and b/public/mix-manifest.json differ
diff --git a/resources/assets/components/admin/AdminReports.vue b/resources/assets/components/admin/AdminReports.vue
new file mode 100644
index 000000000..fdd11b012
--- /dev/null
+++ b/resources/assets/components/admin/AdminReports.vue
@@ -0,0 +1,937 @@
+
+ Moderation @{{report.reported.username}} @{{report.reporter.username}}
+ {{ tabIndex === 0 ? 'No Active Reports Found!' : 'No Closed Reports Found!' }} Spam Post @{{report.status.account.username}}
+ No Spam Reports Found!Active Reports
+
+ {{ prettyCount(stats.open) }}
+
+ Active Spam Detections
+ {{ prettyCount(stats.autospam_open) }}
+ Total Reports
+ {{ prettyCount(stats.total) }}
+
+ Total Spam Detections
+
+ {{ prettyCount(stats.autospam) }}
+
+
+
+
+
+
+
+
+
+ ID
+ Report
+ Reported Account
+ Reported By
+ Created
+ View Report
+
+
+
+
+
+ {{ report.id }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ timeAgo(report.created_at) }}
+ View
+
+
+
+
+
+
+
+
+ ID
+ Report
+ Reported Account
+ Created
+ View Report
+
+
+
+
+
+ {{ report.id }}
+
+
+
+
+
+
+
+ {{ timeAgo(report.created_at) }}
+ View
+
+
+
+
+
+
+
{{$report->type}}
- @if($report->reporter && $report->status) -{{$report->reporter->username}} reported this post
- @else -- @if(!$report->reporter) - Deleted user - @else - {{$report->reporter->username}} - @endif - reported this - @if(!$report->status) - deleted post - @else - post - @endif - -
- - @endif -No reports found
-Try the new Report UI
+We are deprecating this Report UI in the next major version release. The updated Report UI is easier, faster and provides more options to handle reports and spam.
+@@ -16,16 +24,17 @@
{{$report->status->media()->count() ? 'Caption' : 'Comment'}}: {{$report->status->caption}}
@endif + @if($report->status)Like Count: {{$report->status->likes_count}}
@@ -41,7 +50,8 @@Local URL: {{url('/i/web/post/' . $report->status->id)}}
- @if($report->status->in_reply_to_id) + @endif + @if($report->status && $report->status->in_reply_to_id)Parent Post: {{url('/i/web/post/' . $report->status->in_reply_to_id)}}
diff --git a/resources/views/admin/reports/show_spam.blade.php b/resources/views/admin/reports/show_spam.blade.php index c001b39a1..2559bfaa8 100644 --- a/resources/views/admin/reports/show_spam.blade.php +++ b/resources/views/admin/reports/show_spam.blade.php @@ -1,6 +1,13 @@ @extends('admin.partial.template-full') @section('section') +Try the new Report UI
+We are deprecating this Report UI in the next major version release. The updated Report UI is easier, faster and provides more options to handle reports and spam.
+Autospam
@@ -15,7 +22,7 @@- URL: {{$meta->url}} + URL: {{$meta->url}}