mirror of
https://github.com/VueFileManager/vuefilemanager.git
synced 2026-05-13 08:45:01 +00:00
get data from dataabse and upload/download storage chart
This commit is contained in:
@@ -72,7 +72,7 @@
|
||||
"/chunks/settings-invoices.js": "/chunks/settings-invoices.js?id=aa32489d4652f18b4a8c",
|
||||
"/chunks/settings-password.js": "/chunks/settings-password.js?id=fc95bc9d31d3e9ee0442",
|
||||
"/chunks/settings-payment-methods.js": "/chunks/settings-payment-methods.js?id=3a88e55341d1f1ffe12d",
|
||||
"/chunks/settings-storage.js": "/chunks/settings-storage.js?id=07ab72ae0c586211689e",
|
||||
"/chunks/settings-storage.js": "/chunks/settings-storage.js?id=4432e911b1e7a7415d4d",
|
||||
"/chunks/settings-storage~chunks/settings-subscription~chunks/user-storage~chunks/user-subscription.js": "/chunks/settings-storage~chunks/settings-subscription~chunks/user-storage~chunks/user-subscription.js?id=147afaac8c8bacfe6433",
|
||||
"/chunks/settings-subscription.js": "/chunks/settings-subscription.js?id=cd4f59468aaaca384307",
|
||||
"/chunks/settings~chunks/settings-password.js": "/chunks/settings~chunks/settings-password.js?id=c33cd2341b9b04a732e5",
|
||||
@@ -101,7 +101,7 @@
|
||||
"/chunks/user-detail.js": "/chunks/user-detail.js?id=a55ae1a545a65b92511d",
|
||||
"/chunks/user-invoices.js": "/chunks/user-invoices.js?id=0f755c7e07ffb441ac72",
|
||||
"/chunks/user-password.js": "/chunks/user-password.js?id=ce6c12a5b038f5481bd1",
|
||||
"/chunks/user-storage.js": "/chunks/user-storage.js?id=71b90f0987d1ebe7ead6",
|
||||
"/chunks/user-storage.js": "/chunks/user-storage.js?id=a915de897147d9efde5b",
|
||||
"/chunks/user-subscription.js": "/chunks/user-subscription.js?id=82038ce92ca5e1c9ae05",
|
||||
"/chunks/users.js": "/chunks/users.js?id=308dfaebb01c05f5bed5",
|
||||
"/vendors~chunks/admin~chunks/admin-account~chunks/app-appearance~chunks/app-billings~chunks/app-email~35bc7519.js": "/vendors~chunks/admin~chunks/admin-account~chunks/app-appearance~chunks/app-billings~chunks/app-email~35bc7519.js?id=ae06aafc3749254fe4aa",
|
||||
@@ -233,5 +233,22 @@
|
||||
"/chunks/settings-storage.bc9b0469009df5b5eab3.hot-update.js": "/chunks/settings-storage.bc9b0469009df5b5eab3.hot-update.js",
|
||||
"/chunks/settings-storage~chunks/settings-subscription~chunks/user-storage~chunks/user-subscription.27b9a61682a249f71c20.hot-update.js": "/chunks/settings-storage~chunks/settings-subscription~chunks/user-storage~chunks/user-subscription.27b9a61682a249f71c20.hot-update.js",
|
||||
"/chunks/settings-storage.98ffc3f928190cda8a1b.hot-update.js": "/chunks/settings-storage.98ffc3f928190cda8a1b.hot-update.js",
|
||||
"/chunks/settings-storage.f8bb07f42ef648dd2548.hot-update.js": "/chunks/settings-storage.f8bb07f42ef648dd2548.hot-update.js"
|
||||
"/chunks/settings-storage.f8bb07f42ef648dd2548.hot-update.js": "/chunks/settings-storage.f8bb07f42ef648dd2548.hot-update.js",
|
||||
"/chunks/settings-storage.32824a8cada155a6105c.hot-update.js": "/chunks/settings-storage.32824a8cada155a6105c.hot-update.js",
|
||||
"/chunks/settings-storage.19bb2501c7894e79a6a8.hot-update.js": "/chunks/settings-storage.19bb2501c7894e79a6a8.hot-update.js",
|
||||
"/chunks/settings-storage.d3f0fe090dd9dc6c9047.hot-update.js": "/chunks/settings-storage.d3f0fe090dd9dc6c9047.hot-update.js",
|
||||
"/chunks/settings-storage.24d62fa0465b60223e16.hot-update.js": "/chunks/settings-storage.24d62fa0465b60223e16.hot-update.js",
|
||||
"/chunks/settings-storage.9cf196722f61b21d77e7.hot-update.js": "/chunks/settings-storage.9cf196722f61b21d77e7.hot-update.js",
|
||||
"/chunks/settings-storage.d08746a159b5b6f24cf5.hot-update.js": "/chunks/settings-storage.d08746a159b5b6f24cf5.hot-update.js",
|
||||
"/chunks/settings-storage.4c0de25c1c85381273a5.hot-update.js": "/chunks/settings-storage.4c0de25c1c85381273a5.hot-update.js",
|
||||
"/chunks/settings-storage.5fa08ebf41010316d980.hot-update.js": "/chunks/settings-storage.5fa08ebf41010316d980.hot-update.js",
|
||||
"/chunks/settings-storage.ad7ce7a580641c99b976.hot-update.js": "/chunks/settings-storage.ad7ce7a580641c99b976.hot-update.js",
|
||||
"/chunks/settings-storage.24fd0c789730322e24dc.hot-update.js": "/chunks/settings-storage.24fd0c789730322e24dc.hot-update.js",
|
||||
"/chunks/settings-storage.1ddabb2c5f0f70bc686e.hot-update.js": "/chunks/settings-storage.1ddabb2c5f0f70bc686e.hot-update.js",
|
||||
"/chunks/settings-storage.6d7197b3b9a6a18be52e.hot-update.js": "/chunks/settings-storage.6d7197b3b9a6a18be52e.hot-update.js",
|
||||
"/chunks/user-storage.c956ecfce06968b48ec0.hot-update.js": "/chunks/user-storage.c956ecfce06968b48ec0.hot-update.js",
|
||||
"/chunks/user-storage.0e8d7663b9f8cd40de17.hot-update.js": "/chunks/user-storage.0e8d7663b9f8cd40de17.hot-update.js",
|
||||
"/chunks/user-storage.4eff070ff2a92236c8b2.hot-update.js": "/chunks/user-storage.4eff070ff2a92236c8b2.hot-update.js",
|
||||
"/chunks/settings-storage.4ecc7c38e789439da637.hot-update.js": "/chunks/settings-storage.4ecc7c38e789439da637.hot-update.js",
|
||||
"/chunks/user-storage.4ecc7c38e789439da637.hot-update.js": "/chunks/user-storage.4ecc7c38e789439da637.hot-update.js"
|
||||
}
|
||||
|
||||
@@ -1,17 +1,50 @@
|
||||
<template>
|
||||
<PageTab :is-loading="isLoading" v-if="storage">
|
||||
|
||||
<div class="card shadow-card">
|
||||
<div v-if="distribution" class="card shadow-card">
|
||||
<FormLabel icon="hard-drive">
|
||||
{{ $t('Storage Usage') }}
|
||||
</FormLabel>
|
||||
|
||||
<div v-if="distribution">
|
||||
<b class="mb-3 block text-sm text-gray-400">
|
||||
{{ $t('Total') }} {{ storage.data.attributes.used }} {{ $t('of') }} {{ storage.data.attributes.capacity }} {{ $t('Used') }}
|
||||
<b class="text-3xl font-extrabold -mt-3 block mb-0.5">
|
||||
{{ storage.data.attributes.used }}
|
||||
</b>
|
||||
|
||||
<b class="mb-3 block text-sm text-gray-400 mb-5">
|
||||
{{ $t('Total of') }} {{ storage.data.attributes.capacity }} {{ $t('Used') }}
|
||||
</b>
|
||||
|
||||
<ProgressLine :data="distribution" />
|
||||
</div>
|
||||
<div v-if="distribution" class="card shadow-card">
|
||||
<FormLabel icon="hard-drive">
|
||||
{{ $t('Upload') }}
|
||||
</FormLabel>
|
||||
|
||||
<b class="text-3xl font-extrabold -mt-3 block mb-0.5">
|
||||
{{ storage.data.meta.traffic.upload }}
|
||||
</b>
|
||||
|
||||
<b class="mb-3 block text-sm text-gray-400 mb-5">
|
||||
{{ $t('In last 45 days') }}
|
||||
</b>
|
||||
|
||||
<BarChart :data="storage.data.meta.traffic.chart.upload" color="#FFBD2D" />
|
||||
</div>
|
||||
<div v-if="distribution" class="card shadow-card">
|
||||
<FormLabel icon="hard-drive">
|
||||
{{ $t('Download') }}
|
||||
</FormLabel>
|
||||
|
||||
<b class="text-3xl font-extrabold -mt-3 block mb-0.5">
|
||||
{{ storage.data.meta.traffic.download }}
|
||||
</b>
|
||||
|
||||
<b class="mb-3 block text-sm text-gray-400 mb-5">
|
||||
{{ $t('In last 45 days') }}
|
||||
</b>
|
||||
|
||||
<BarChart :data="storage.data.meta.traffic.chart.download" color="#9d66fe" />
|
||||
</div>
|
||||
|
||||
<div v-if="config.storageLimit && ! user.data.attributes.subscription" class="card shadow-card">
|
||||
@@ -55,9 +88,10 @@
|
||||
import ButtonBase from '/resources/js/components/FilesView/ButtonBase'
|
||||
import SetupBox from '/resources/js/components/Others/Forms/SetupBox'
|
||||
import {required} from 'vee-validate/dist/rules'
|
||||
import BarChart from "../../../User/BarChart"
|
||||
import {events} from '/resources/js/bus'
|
||||
import {mapGetters} from "vuex"
|
||||
import axios from 'axios'
|
||||
import {mapGetters} from "vuex";
|
||||
|
||||
export default {
|
||||
name: 'UserStorage',
|
||||
@@ -76,6 +110,7 @@
|
||||
ButtonBase,
|
||||
SetupBox,
|
||||
required,
|
||||
BarChart,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['config']),
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<div class="flex items-end justify-between h-28">
|
||||
<span
|
||||
class="w-2.5 block rounded-lg lg:mr-2 mr-1.5"
|
||||
:style="{height: height + '%', backgroundColor: color}"
|
||||
v-for="(height, i) in data"
|
||||
:key="i">
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'BarChart',
|
||||
props: {
|
||||
data: {},
|
||||
color: {},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -21,16 +21,14 @@
|
||||
</FormLabel>
|
||||
|
||||
<b class="text-3xl font-extrabold -mt-3 block mb-0.5">
|
||||
154.98MB
|
||||
{{ storage.data.meta.traffic.upload }}
|
||||
</b>
|
||||
|
||||
<b class="mb-3 block text-sm text-gray-400 mb-5">
|
||||
{{ $t('In last 30 days') }}
|
||||
{{ $t('In last 45 days') }}
|
||||
</b>
|
||||
|
||||
<div class="flex items-end justify-between h-28">
|
||||
<span class="w-2.5 block rounded-lg lg:mr-2 mr-1.5" :style="{height: Math.random() * 100 + '%', backgroundColor: '#9d66fe'}" v-for="(item, i) in Array(45).fill(0)" :key="i"></span>
|
||||
</div>
|
||||
<BarChart :data="storage.data.meta.traffic.chart.upload" color="#FFBD2D" />
|
||||
</div>
|
||||
<div v-if="distribution" class="card shadow-card">
|
||||
<FormLabel icon="hard-drive">
|
||||
@@ -38,16 +36,14 @@
|
||||
</FormLabel>
|
||||
|
||||
<b class="text-3xl font-extrabold -mt-3 block mb-0.5">
|
||||
927.12MB
|
||||
{{ storage.data.meta.traffic.download }}
|
||||
</b>
|
||||
|
||||
<b class="mb-3 block text-sm text-gray-400 mb-5">
|
||||
{{ $t('In last 30 days') }}
|
||||
{{ $t('In last 45 days') }}
|
||||
</b>
|
||||
|
||||
<div class="flex items-end justify-between h-28">
|
||||
<span class="w-2.5 bg-theme block rounded-lg lg:mr-2 mr-1.5" :style="{height: Math.random() * 100 + '%', backgroundColor: '#ffb822'}" v-for="(item, i) in Array(45).fill(0)" :key="i"></span>
|
||||
</div>
|
||||
<BarChart :data="storage.data.meta.traffic.chart.download" color="#9d66fe" />
|
||||
</div>
|
||||
</PageTab>
|
||||
</template>
|
||||
@@ -56,16 +52,17 @@
|
||||
import ProgressLine from "../../components/Admin/ProgressLine";
|
||||
import FormLabel from '/resources/js/components/Others/Forms/FormLabel'
|
||||
import PageTab from '/resources/js/components/Others/Layout/PageTab'
|
||||
import Spinner from '/resources/js/components/FilesView/Spinner'
|
||||
import axios from 'axios'
|
||||
import BarChart from "./BarChart";
|
||||
|
||||
export default {
|
||||
name: 'Storage',
|
||||
components: {
|
||||
BarChart,
|
||||
ProgressLine,
|
||||
FormLabel,
|
||||
PageTab,
|
||||
Spinner,
|
||||
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
@@ -71,6 +71,7 @@ class SetupDevEnvironment extends Command
|
||||
|
||||
$this->info('Creating default demo content...');
|
||||
$this->create_admin_default_content();
|
||||
$this->generate_traffic();
|
||||
$this->create_team_folders_content();
|
||||
$this->create_share_with_me_team_folders_content();
|
||||
$this->create_share_records();
|
||||
@@ -118,6 +119,23 @@ class SetupDevEnvironment extends Command
|
||||
$this->info('Default admin account created. Email: howdy@hi5ve.digital and Password: vuefilemanager');
|
||||
}
|
||||
|
||||
private function generate_traffic(): void
|
||||
{
|
||||
$user = User::whereEmail('howdy@hi5ve.digital')
|
||||
->first();
|
||||
|
||||
foreach (range(0, 45) as $day) {
|
||||
DB::table('traffic')->insert([
|
||||
'id' => Str::uuid(),
|
||||
'user_id' => $user->id,
|
||||
'upload' => random_int(1111111, 9999999),
|
||||
'download' => random_int(11111111, 99999999),
|
||||
'created_at' => now()->subDays($day),
|
||||
'updated_at' => now()->subDays($day),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create default admin account
|
||||
*/
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Users\Models;
|
||||
|
||||
use ByteUnits\Metric;
|
||||
use Domain\Traffic\Models\Traffic;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Domain\Files\Models\File;
|
||||
@@ -186,6 +187,11 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
return $this->hasMany(Folder::class);
|
||||
}
|
||||
|
||||
public function traffics(): HasMany
|
||||
{
|
||||
return $this->hasMany(Traffic::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the password reset notification.
|
||||
*/
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Users\Resources;
|
||||
|
||||
use ByteUnits\Metric;
|
||||
use Domain\Files\Models\File;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class UserStorageResource extends JsonResource
|
||||
@@ -15,64 +16,120 @@ class UserStorageResource extends JsonResource
|
||||
*/
|
||||
public function toArray($request)
|
||||
{
|
||||
$document_mimetypes = [
|
||||
'pdf', 'numbers', 'xlsx', 'xls', 'txt', 'md', 'rtf', 'pptx', 'ppt', 'odt', 'ods', 'odp', 'epub', 'docx', 'doc', 'csv', 'pages',
|
||||
];
|
||||
list($images, $audios, $videos, $documents, $others) = $this->get_file_type_distribution();
|
||||
|
||||
// Get all images
|
||||
$images = File::where('user_id', $this->id)
|
||||
->where('type', 'image')->get()->map(fn ($item) => (int) $item->getRawOriginal('filesize'))->sum();
|
||||
|
||||
// Get all audios
|
||||
$audios = File::where('user_id', $this->id)
|
||||
->where('type', 'audio')->get()->map(fn ($item) => (int) $item->getRawOriginal('filesize'))->sum();
|
||||
|
||||
// Get all videos
|
||||
$videos = File::where('user_id', $this->id)
|
||||
->where('type', 'video')->get()->map(fn ($item) => (int) $item->getRawOriginal('filesize'))->sum();
|
||||
|
||||
// Get all documents
|
||||
$documents = File::where('user_id', $this->id)
|
||||
->whereIn('mimetype', $document_mimetypes)->get()->map(fn ($item) => (int) $item->getRawOriginal('filesize'))->sum();
|
||||
|
||||
// Get all other files
|
||||
$others = File::where('user_id', $this->id)
|
||||
->whereNotIn('mimetype', $document_mimetypes)
|
||||
->whereNotIn('type', ['audio', 'video', 'image'])
|
||||
->get()->map(fn ($item) => (int) $item->getRawOriginal('filesize'))->sum();
|
||||
list($downloadTotal, $uploadTotal, $upload, $download) = $this->get_traffic_distribution();
|
||||
|
||||
return [
|
||||
'data' => [
|
||||
'id' => (string) $this->id,
|
||||
'id' => (string)$this->id,
|
||||
'type' => 'storage',
|
||||
'attributes' => [
|
||||
'used' => Metric::bytes($this->usedCapacity)->format(),
|
||||
'capacity' => format_gigabytes($this->limitations->max_storage_amount),
|
||||
'percentage' => (float) get_storage_fill_percentage($this->usedCapacity, $this->limitations->max_storage_amount),
|
||||
'percentage' => (float)get_storage_fill_percentage($this->usedCapacity, $this->limitations->max_storage_amount),
|
||||
],
|
||||
'meta' => [
|
||||
'traffic' => [
|
||||
'chart' => [
|
||||
'download' => $download,
|
||||
'upload' => $upload,
|
||||
],
|
||||
'download' => Metric::bytes($downloadTotal)->format(),
|
||||
'upload' => Metric::bytes($uploadTotal)->format(),
|
||||
],
|
||||
'images' => [
|
||||
'used' => Metric::bytes($images)->format(),
|
||||
'percentage' => (float) get_storage_fill_percentage($images, $this->limitations->max_storage_amount),
|
||||
'percentage' => (float)get_storage_fill_percentage($images, $this->limitations->max_storage_amount),
|
||||
],
|
||||
'audios' => [
|
||||
'used' => Metric::bytes($audios)->format(),
|
||||
'percentage' => (float) get_storage_fill_percentage($audios, $this->limitations->max_storage_amount),
|
||||
'percentage' => (float)get_storage_fill_percentage($audios, $this->limitations->max_storage_amount),
|
||||
],
|
||||
'videos' => [
|
||||
'used' => Metric::bytes($videos)->format(),
|
||||
'percentage' => (float) get_storage_fill_percentage($videos, $this->limitations->max_storage_amount),
|
||||
'percentage' => (float)get_storage_fill_percentage($videos, $this->limitations->max_storage_amount),
|
||||
],
|
||||
'documents' => [
|
||||
'used' => Metric::bytes($documents)->format(),
|
||||
'percentage' => (float) get_storage_fill_percentage($documents, $this->limitations->max_storage_amount),
|
||||
'percentage' => (float)get_storage_fill_percentage($documents, $this->limitations->max_storage_amount),
|
||||
],
|
||||
'others' => [
|
||||
'used' => Metric::bytes($others)->format(),
|
||||
'percentage' => (float) get_storage_fill_percentage($others, $this->limitations->max_storage_amount),
|
||||
'percentage' => (float)get_storage_fill_percentage($others, $this->limitations->max_storage_amount),
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function get_file_type_distribution(): array
|
||||
{
|
||||
$document_mimetypes = [
|
||||
'pdf', 'numbers', 'xlsx', 'xls', 'txt', 'md', 'rtf', 'pptx', 'ppt', 'odt', 'ods', 'odp', 'epub', 'docx', 'doc', 'csv', 'pages',
|
||||
];
|
||||
|
||||
$images = DB::table('files')
|
||||
->where('user_id', $this->id)
|
||||
->where('type', 'image')
|
||||
->sum('filesize');
|
||||
|
||||
$audios = DB::table('files')
|
||||
->where('user_id', $this->id)
|
||||
->where('type', 'audio')
|
||||
->sum('filesize');
|
||||
|
||||
$videos = DB::table('files')
|
||||
->where('user_id', $this->id)
|
||||
->where('type', 'video')
|
||||
->sum('filesize');
|
||||
|
||||
$documents = DB::table('files')
|
||||
->where('user_id', $this->id)
|
||||
->whereIn('mimetype', $document_mimetypes)
|
||||
->sum('filesize');
|
||||
|
||||
$others = DB::table('files')
|
||||
->where('user_id', $this->id)
|
||||
->whereNotIn('mimetype', $document_mimetypes)
|
||||
->whereNotIn('type', ['audio', 'video', 'image'])
|
||||
->sum('filesize');
|
||||
|
||||
return [$images, $audios, $videos, $documents, $others];
|
||||
}
|
||||
|
||||
private function get_traffic_distribution(): array
|
||||
{
|
||||
$period = now()->subDays(45)->endOfDay();
|
||||
|
||||
$uploadMax = \DB::table('traffic')
|
||||
->where('user_id', $this->id)
|
||||
->where('created_at', '>', $period)
|
||||
->max('upload');
|
||||
|
||||
$downloadMax = \DB::table('traffic')
|
||||
->where('user_id', $this->id)
|
||||
->where('created_at', '>', $period)
|
||||
->max('download');
|
||||
|
||||
$trafficRecords = \DB::table('traffic')
|
||||
->where('user_id', $this->id)
|
||||
->where('created_at', '>', $period)
|
||||
->get();
|
||||
|
||||
$downloadTotal = \DB::table('traffic')
|
||||
->where('user_id', $this->id)
|
||||
->where('created_at', '>', $period)
|
||||
->sum('download');
|
||||
|
||||
$uploadTotal = \DB::table('traffic')
|
||||
->where('user_id', $this->id)
|
||||
->where('created_at', '>', $period)
|
||||
->sum('upload');
|
||||
|
||||
$upload = $trafficRecords->map(fn($record) => round(($record->upload / $uploadMax) * 100, 2));
|
||||
$download = $trafficRecords->map(fn($record) => round(($record->download / $downloadMax) * 100, 2));
|
||||
|
||||
return [$downloadTotal, $uploadTotal, $upload, $download];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace Tests\Domain\Traffic;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Storage;
|
||||
use Tests\TestCase;
|
||||
use App\Users\Models\User;
|
||||
@@ -186,4 +187,28 @@ class TrafficTest extends TestCase
|
||||
'download' => $document->getSize(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_get_user_traffic_test()
|
||||
{
|
||||
$user = User::factory()
|
||||
->create();
|
||||
|
||||
foreach (range(0, 30) as $day) {
|
||||
DB::table('traffic')->insert([
|
||||
'id' => Str::uuid(),
|
||||
'user_id' => $user->id,
|
||||
'upload' => random_int(11111111, 99999999),
|
||||
'download' => random_int(11111111, 99999999),
|
||||
'created_at' => now()->subDays($day),
|
||||
'updated_at' => now()->subDays($day),
|
||||
]);
|
||||
}
|
||||
|
||||
$this->actingAs($user)
|
||||
->get('/api/user/storage')
|
||||
->assertOk();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user