mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-12-27 15:33:17 +00:00
Merge pull request #439 from pixelfed/frontend-ui-refactor
Frontend ui refactor
This commit is contained in:
commit
bc65ded6d6
5 changed files with 331 additions and 20 deletions
|
@ -3,9 +3,11 @@
|
|||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Media;
|
||||
use App\Profile;
|
||||
use App\Report;
|
||||
use App\Status;
|
||||
use App\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Admin\{
|
||||
AdminReportController
|
||||
|
@ -27,9 +29,17 @@ class AdminController extends Controller
|
|||
|
||||
public function users(Request $request)
|
||||
{
|
||||
$stats = $this->collectUserStats($request);
|
||||
$users = User::orderBy('id', 'desc')->paginate(10);
|
||||
return view('admin.users.home', compact('users', 'stats'));
|
||||
}
|
||||
|
||||
return view('admin.users.home', compact('users'));
|
||||
|
||||
public function editUser(Request $request, $id)
|
||||
{
|
||||
$user = User::find($id);
|
||||
$profile = $user->profile;
|
||||
return view('admin.users.edit', compact('user', 'profile'));
|
||||
}
|
||||
|
||||
public function statuses(Request $request)
|
||||
|
@ -64,4 +74,33 @@ class AdminController extends Controller
|
|||
$report = Report::findOrFail($id);
|
||||
return view('admin.reports.show', compact('report'));
|
||||
}
|
||||
|
||||
|
||||
protected function collectUserStats($request)
|
||||
{
|
||||
$total_duration = $request->query('total_duration') ?? '30';
|
||||
$new_duration = $request->query('new_duration') ?? '7';
|
||||
$stats = [];
|
||||
$stats['total'] = [
|
||||
'count' => User::where('created_at', '>', Carbon::now()->subDays($total_duration))->count(),
|
||||
'points' => User::selectRaw('day(created_at) day, count(*) as count')->where('created_at','>', Carbon::now()->subDays($total_duration))->groupBy('day')->pluck('count')
|
||||
];
|
||||
$stats['new'] = [
|
||||
'count' => User::where('created_at', '>', Carbon::now()->subDays($new_duration))->count(),
|
||||
'points' => User::selectRaw('day(created_at) day, count(*) as count')->where('created_at','>', Carbon::now()->subDays($new_duration))->groupBy('day')->pluck('count')
|
||||
];
|
||||
$stats['active'] = [
|
||||
'count' => Status::groupBy('profile_id')->count()
|
||||
];
|
||||
$stats['profile'] = [
|
||||
'local' => Profile::whereNull('remote_url')->count(),
|
||||
'remote' => Profile::whereNotNull('remote_url')->count()
|
||||
];
|
||||
$stats['avg'] = [
|
||||
'age' => Carbon::parse(substr(User::avg('created_at'),0,8))->diffForHumans(null,true,true),
|
||||
'posts' => floor(Status::avg('profile_id'))
|
||||
];
|
||||
return $stats;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
@section('section')
|
||||
<div class="title font-weight-bold">
|
||||
<h3 class="">Reports</h3>
|
||||
<h3 class="font-weight-bold">Reports</h3>
|
||||
<p>
|
||||
<span class="pr-3">
|
||||
<span>Open:</span>
|
||||
|
|
101
resources/views/admin/users/edit.blade.php
Normal file
101
resources/views/admin/users/edit.blade.php
Normal file
|
@ -0,0 +1,101 @@
|
|||
@extends('admin.partial.template')
|
||||
|
||||
@section('section')
|
||||
<div class="title d-flex justify-content-between">
|
||||
<h3 class="font-weight-bold">Edit User</h3>
|
||||
<span><a href="{{route('admin.users')}}" class="btn btn-outline-primary btn-sm font-weight-bold">Back</a></span>
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-12 col-md-3">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<p class="h4 mb-0 font-weight-bold">{{$profile->statusCount()}}</p>
|
||||
<p class="text-muted font-weight-bold small mb-0">Posts</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-3">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<p class="h4 mb-0 font-weight-bold">{{$profile->likes()->count()}}</p>
|
||||
<p class="text-muted font-weight-bold small mb-0">Likes</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-3">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<p class="h4 mb-0 font-weight-bold">{{$profile->reports()->count()}}</p>
|
||||
<p class="text-muted font-weight-bold small mb-0">Reports</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-3">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<p class="h4 mb-0 font-weight-bold">{{PrettyNumber::size($profile->media()->sum('size'))}}</p>
|
||||
<p class="text-muted font-weight-bold small mb-0">Storage Used</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-2">
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<img src="{{$profile->avatarUrl()}}" class="img-thumbnail rounded-circle" width="128px" height="128px">
|
||||
</div>
|
||||
<div class="card-footer bg-white">
|
||||
<p class="font-weight-bold mb-0 small">Last updated: {{$profile->avatar->updated_at->diffForHumans()}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-body p-5 d-flex justify-content-center align-items-center">
|
||||
<div class="text-center py-3">
|
||||
<p class="font-weight-bold mb-0">
|
||||
{{$profile->username}}
|
||||
</p>
|
||||
<p class="h3 font-weight-bold">
|
||||
{{$profile->emailUrl()}}
|
||||
</p>
|
||||
<p class="font-weight-bold mb-0 text-muted">
|
||||
Member Since: {{$profile->created_at->format('M Y')}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="mx-3">
|
||||
<div class="sub-title h4 font-weight-bold mb-4">
|
||||
Account Settings
|
||||
</div>
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label class="font-weight-bold text-muted">Display Name</label>
|
||||
<input type="text" class="form-control" value="{{$user->name}}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="font-weight-bold text-muted">Username</label>
|
||||
<input type="text" class="form-control" value="{{$user->username}}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="font-weight-bold text-muted">Email address</label>
|
||||
<input type="email" class="form-control" value="{{$user->email}}" placeholder="Enter email">
|
||||
<p class="help-text small text-muted font-weight-bold">
|
||||
@if($user->email_verified_at)
|
||||
<span class="text-success">Verified</span> for {{$user->email_verified_at->diffForHumans()}}
|
||||
@else
|
||||
<span class="text-danger">Unverified</span> email.
|
||||
@endif
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@endsection
|
|
@ -4,31 +4,157 @@
|
|||
<div class="title">
|
||||
<h3 class="font-weight-bold">Users</h3>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div class="row mb-3">
|
||||
<div class="col-12 col-md-6 mb-2">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="font-weight-bold text-muted">Total Users</span>
|
||||
{{-- <span>
|
||||
<select class="feature-filter form-control form-control-sm bg-light border-0" data-id="total" data-duration="{{request()->query('total_duration') ?? 30}}">
|
||||
<option data-duration="1">1 Day</option>
|
||||
<option data-duration="14">2 Weeks</option>
|
||||
<option data-duration="30" selected="">1 Month</option>
|
||||
<option data-duration="365">1 Year</option>
|
||||
</select>
|
||||
</span> --}}
|
||||
</div>
|
||||
<div>
|
||||
<p class="h3 font-weight-bold mb-0">{{$stats['total']['count']}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="totalUsers pb-2"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 mb-2">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="font-weight-bold text-muted">New Users</span>
|
||||
{{-- <span>
|
||||
<select class="form-control form-control-sm bg-light border-0">
|
||||
<option>1 Day</option>
|
||||
<option>2 Weeks</option>
|
||||
<option selected="">1 Month</option>
|
||||
<option>1 Year</option>
|
||||
</select>
|
||||
</span> --}}
|
||||
</div>
|
||||
<div>
|
||||
<p class="h3 font-weight-bold mb-0">{{$stats['new']['count']}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="newUsers pb-2"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-3 mb-2">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="font-weight-bold text-muted">Local</span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="h3 font-weight-bold mb-0">{{$stats['profile']['local']}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-3 mb-2">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="font-weight-bold text-muted">Remote</span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="h3 font-weight-bold mb-0">{{$stats['profile']['remote']}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-3 mb-2">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="font-weight-bold text-muted">Avg Age</span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="h3 font-weight-bold mb-0">{{$stats['avg']['age']}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-3 mb-2">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="font-weight-bold text-muted">Avg Posts</span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="h3 font-weight-bold mb-0">{{$stats['avg']['posts']}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th scope="col">Username</th>
|
||||
<th scope="col">Statuses</th>
|
||||
<th scope="col">Storage</th>
|
||||
<th scope="col">Role</th>
|
||||
<th scope="col">Created</th>
|
||||
<thead class="bg-light">
|
||||
<tr class="text-center">
|
||||
<th scope="col" class="border-0">
|
||||
<span>ID</span>
|
||||
</th>
|
||||
<th scope="col" class="border-0">
|
||||
<span>Avatar</span>
|
||||
</th>
|
||||
<th scope="col" class="border-0">
|
||||
<span>Username</span>
|
||||
</th>
|
||||
<th scope="col" class="border-0">
|
||||
<span>Status Count</span>
|
||||
</th>
|
||||
<th scope="col" class="border-0">
|
||||
<span>Storage Used</span>
|
||||
</th>
|
||||
<th scope="col" class="border-0">
|
||||
<span>Actions</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($users as $user)
|
||||
<tr>
|
||||
<tr class="font-weight-bold text-center">
|
||||
<th scope="row">
|
||||
<a href="{{$user->url()}}">
|
||||
{{$user->username}}
|
||||
</a>
|
||||
{{$user->id}}
|
||||
</th>
|
||||
<td>{{$user->profile->statuses->count()}}</td>
|
||||
<td><p class="human-size" data-bytes="{{App\Media::whereUserId($user->id)->sum('size')}}"></p></td>
|
||||
<td>{!!$user->is_admin ? '<span class="text-danger">admin</span>' : 'member'!!}</td>
|
||||
<td>{{$user->created_at->diffForHumans(null, true, true)}}</td>
|
||||
<td>
|
||||
<img src="{{$user->profile->avatarUrl()}}" width="28px" class="rounded-circle" style="border:1px solid #ccc">
|
||||
</td>
|
||||
<td>
|
||||
<span title="{{$user->username}}" data-toggle="tooltip" data-placement="bottom">
|
||||
{{$user->username}}
|
||||
@if($user->is_admin)
|
||||
<i class="text-danger fas fa-certificate" title="Admin"></i>
|
||||
@endif
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{{$user->profile->statusCount()}}
|
||||
</td>
|
||||
<td>
|
||||
<p class="human-size mb-0" data-bytes="{{App\Media::whereUserId($user->id)->sum('size')}}"></p>
|
||||
</td>
|
||||
<td>
|
||||
<span>
|
||||
<a href="#" class="pr-2 text-muted action-btn" title="View Profile" data-toggle="tooltip" data-placement="bottom" data-id="{{$user->id}}" data-action="view" data-url="{{$user->url()}}">
|
||||
<i class="fas fa-eye"></i>
|
||||
</a>
|
||||
<a href="#" class="pr-2 text-muted action-btn" title="Edit Profile" data-toggle="tooltip" data-placement="bottom" data-id="{{$user->id}}" data-action="edit">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<a href="#" class="text-muted action-btn" title="Delete Profile" data-toggle="tooltip" data-placement="bottom" data-id="{{$user->id}}" data-action="delete">
|
||||
<i class="fas fa-trash"></i>
|
||||
</a>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
|
@ -39,14 +165,58 @@
|
|||
</div>
|
||||
@endsection
|
||||
|
||||
@push('styles')
|
||||
<style type="text/css">
|
||||
.jqstooltip {
|
||||
-webkit-box-sizing: content-box;
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
border: 0 !important;
|
||||
border-radius: 2px;
|
||||
max-width: 20px;
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
@push('scripts')
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-sparklines/2.1.2/jquery.sparkline.min.js" integrity="sha256-BuAkLaFyq4WYXbN3TFSsG1M5GltEeFehAMURi4KBpUM=" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
|
||||
$('.human-size').each(function(d,a) {
|
||||
let el = $(a);
|
||||
let size = el.data('bytes');
|
||||
el.text(filesize(size, {round: 0}));
|
||||
});
|
||||
|
||||
$(document).on('click', '.action-btn', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
let el = $(this);
|
||||
let id = el.data('id');
|
||||
let action = el.data('action');
|
||||
|
||||
switch(action) {
|
||||
case 'view':
|
||||
window.location.href = el.data('url');
|
||||
break;
|
||||
case 'edit':
|
||||
let redirect = '/i/admin/users/edit/' + id;
|
||||
window.location.href = redirect;
|
||||
break;
|
||||
case 'delete':
|
||||
swal('Error', 'Sorry this action is not yet available', 'error');
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
let sparkopts = {
|
||||
width: '100%',
|
||||
height: 30,
|
||||
lineColor: '#0083CD',
|
||||
fillColor: false
|
||||
};
|
||||
$('.totalUsers').sparkline({{$stats['total']['points']}}, sparkopts);
|
||||
$('.newUsers').sparkline({{$stats['new']['points']}}, sparkopts);
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
|
|
@ -13,6 +13,7 @@ Route::domain(config('pixelfed.domain.admin'))->prefix('i/admin')->group(functio
|
|||
Route::get('statuses/show/{id}', 'AdminController@showStatus');
|
||||
Route::redirect('users', '/users/list');
|
||||
Route::get('users/list', 'AdminController@users')->name('admin.users');
|
||||
Route::get('users/edit/{id}', 'AdminController@editUser');
|
||||
Route::redirect('media', '/media/list');
|
||||
Route::get('media/list', 'AdminController@media')->name('admin.media');
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue