Admin panel changes.

- Indev user assets.
- Moved admin find user endpoint.
- Shifted around some buttons on the admin panel navigation.
- Re-implement .border-0.
- Indev user admin page. Somewhat functional mess.
- Updated admin search input component to have the label as optional.
- Grouped user search routes.
- Admin user lookup tool.
- Tabbed admin pages.
- Update profile quick moderation user admin button to work with the new user admin page.
- Remove unfinished quick moderate button from user profile.
This commit is contained in:
Graphictoria 2022-12-28 15:28:11 -05:00
parent 056d12d309
commit ffe0c2f77c
21 changed files with 587 additions and 24 deletions

View File

@ -29,13 +29,24 @@ class AdminController extends Controller
return view('web.admin.dashboard');
}
// GET admin.useradmin
function userAdmin(Request $request)
{
$request->validate([
'ID' => ['required', 'int', 'exists:users,id']
]);
$user = User::where('id', $request->get('ID'))->first();
return view('web.admin.useradmin')->with('user', $user);
}
// GET admin.usersearch
function userSearch()
{
return view('web.admin.usersearch');
}
// POST admin.usersearch
// POST admin.usersearchquery
function userSearchQuery(Request $request)
{
if($request->has('userid-button'))
@ -70,6 +81,51 @@ class AdminController extends Controller
return view('web.admin.usersearch')->with('error', 'Input validation failed.');
}
// GET admin.userlookup
function userLookup()
{
return view('web.admin.userlookup');
}
// POST admin.userlookupquery
function userLookupQuery(Request $request)
{
$request->validate([
'lookup' => ['required', 'string']
]);
$users = [];
foreach(preg_split('/\r\n|\r|\n/', $request->get('lookup')) as $username)
{
$user = User::where('username', $username);
if($user->exists())
{
$user = $user->first();
array_push(
$users,
[
'found' => true,
'user' => $user
]
);
}
else
{
array_push(
$users,
[
'found' => false,
'username' => $username
]
);
}
}
return view('web.admin.userlookup')->with('users', $users)->with('input', $request->get('lookup'));
}
// GET admin.autoupload
function autoUpload()
{

View File

@ -0,0 +1,11 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class UserAsset extends Model
{
use HasFactory;
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\View\Components\admin\navigation;
use Illuminate\View\Component;
class UserAdmin extends Component
{
/**
* Create a new component instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Get the view / contents that represent the component.
*
* @return \Illuminate\Contracts\View\View|\Closure|string
*/
public function render()
{
return view('components.admin.navigation.user-admin');
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\View\Components\Admin\Navigation;
use Illuminate\View\Component;
class UserSearch extends Component
{
/**
* Create a new component instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Get the view / contents that represent the component.
*
* @return \Illuminate\Contracts\View\View|\Closure|string
*/
public function render()
{
return view('components.admin.navigation.user-search');
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\View\Components\Admin;
use Illuminate\View\Component;
class NavigationTabLink extends Component
{
/**
* Create a new component instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Get the view / contents that represent the component.
*
* @return \Illuminate\Contracts\View\View|\Closure|string
*/
public function render()
{
return view('components.admin.navigation-tab-link');
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\View\Components\Admin;
use Illuminate\View\Component;
class NavigationTabs extends Component
{
/**
* Create a new component instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Get the view / contents that represent the component.
*
* @return \Illuminate\Contracts\View\View|\Closure|string
*/
public function render()
{
return view('components.admin.navigation-tabs');
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\View\Components\Admin;
use Illuminate\View\Component;
class UserAdminLabel extends Component
{
/**
* Create a new component instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Get the view / contents that represent the component.
*
* @return \Illuminate\Contracts\View\View|\Closure|string
*/
public function render()
{
return view('components.admin.user-admin-label');
}
}

View File

@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('user_assets', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('owner_id');
$table->unsignedBigInteger('asset_id');
$table->unsignedBigInteger('serial');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('user_assets');
}
};

View File

@ -471,40 +471,44 @@ html {
&-top {
html.vbrick-dark & {
border-top: $gray-700;
border-top: 1px solid $gray-700;
}
html.vbrick-light & {
border-top: $border-color;
border-top: 1px solid $border-color;
}
}
&-end {
html.vbrick-dark & {
border-right: $gray-700;
border-right: 1px solid $gray-700;
}
html.vbrick-light & {
border-right: $border-color;
border-right: 1px solid $border-color;
}
}
&-bottom {
html.vbrick-dark & {
border-bottom: $gray-700;
border-bottom: 1px solid $gray-700;
}
html.vbrick-light & {
border-bottom: $border-color;
border-bottom: 1px solid $border-color;
}
}
&-start {
html.vbrick-dark & {
border-left: $gray-700;
border-left: 1px solid $gray-700;
}
html.vbrick-light & {
border-left: $border-color;
border-left: 1px solid $border-color;
}
}
&-0 {
border: 0!important;
}
&-top-0 {
border-top: 0!important;
}

View File

@ -0,0 +1,17 @@
@props([
'label',
'route'
])
<li class="nav-item">
<a
@class([
'nav-link',
'active' => str_starts_with(Request::path(), substr(parse_url($route, PHP_URL_PATH), 1))
])
aria-current="page"
href="{{ $route }}"
>
{{ $label }}
</a>
</li>

View File

@ -0,0 +1,3 @@
<ul class="nav nav-tabs mb-2">
{{ $slot }}
</ul>

View File

@ -0,0 +1,17 @@
@props([
'uid'
])
<x-admin.navigation-tabs>
<x-admin.navigation-tab-link label="Account" :route="route('admin.useradmin', ['ID' => $uid])" />
<x-admin.navigation-tab-link label="Moderate" :route="route('admin.dashboard')" />
<x-admin.navigation-tab-link label="Transactions" :route="route('admin.dashboard')" />
<x-admin.navigation-tab-link label="Trades" :route="route('admin.dashboard')" />
<x-admin.navigation-tab-link label="Send Personal Msg" :route="route('admin.dashboard')" />
<x-admin.navigation-tab-link label="Messages" :route="route('admin.dashboard')" />
<x-admin.navigation-tab-link label="Adjust Assets" :route="route('admin.dashboard')" />
@owner
<x-admin.navigation-tab-link label="IP" :route="route('admin.dashboard')" />
<x-admin.navigation-tab-link label="MAC Address" :route="route('admin.dashboard')" />
@endowner
</x-admin.navigation-tabs>

View File

@ -0,0 +1,4 @@
<x-admin.navigation-tabs>
<x-admin.navigation-tab-link label="Find Users" :route="route('admin.usersearch')" />
<x-admin.navigation-tab-link label="Lookup Tool" :route="route('admin.userlookup')" />
</x-admin.navigation-tabs>

View File

@ -0,0 +1,12 @@
@props([
'label'
])
<div class="row py-2 border-top">
<div class="col-6">
<p class="fw-bold">{{ $label }}</p>
</div>
<div class="col-6">
{{ $slot }}
</div>
</div>

View File

@ -1,14 +1,28 @@
@props([
'id',
'definition'
'definition',
'nolabel',
'value'
])
<div class="col-6">
<label for="vb-{{ $id }}-search" class="form-label">{{ $definition }}</label>
@if(!isset($nolabel) || !$nolabel)
<label for="vb-{{ $id }}-search" class="form-label">{{ $definition }}</label>
@endif
<form method="POST" action="{{ route('admin.usersearch') }}" enctype="multipart/form-data">
@csrf
<div class="input-group mb-3">
<input type="text" class="form-control" placeholder="{{ $definition }} Here" aria-label="{{ $definition }} Here" name="{{ $id }}-search" id="vb-{{ $id }}-search" aria-describedby="vb-{{ $id }}-search-btn">
<input
type="text"
class="form-control"
placeholder="{{ $definition }} Here"
aria-label="{{ $definition }} Here"
name="{{ $id }}-search" id="vb-{{ $id }}-search"
aria-describedby="vb-{{ $id }}-search-btn"
@if(isset($value))
value="{{ $value }}"
@endif
>
<button type="submit" class="btn btn-primary" type="button" name="{{ $id }}-button" id="vb-{{ $id }}-search-btn">Search</button>
</div>
</form>

View File

@ -0,0 +1,183 @@
@extends('layouts.admin')
@section('title', 'User Admin (' . $user->username . ')')
@section('page-specific')
<style>
.vb-admin-divider > *:first-child {
border-top: 0!important;
}
</style>
@endsection
@push('content')
<div class="container-md">
<x-admin.navigation.user-admin :uid="$user->id" />
<div class="row">
<div class="col-8">
<h4>Account Summary</h4>
<div class="card me-2 px-3 vb-admin-divider">
@php
$isProtected = false;
$isPowerful = false;
$powerColor = 'secondary';
$powerType = '[ Error ]';
if($user->hasRoleset('ProtectedUser'))
$isProtected = true;
if($user->hasRoleset('Owner'))
{
$isPowerful = true;
$powerColor = 'danger';
$powerType = 'owner';
}
elseif($user->hasRoleset('Administrator'))
{
$isPowerful = true;
$powerColor = 'primary';
$powerType = 'administrator';
}
elseif($user->hasRoleset('Moderator'))
{
$isPowerful = true;
$powerColor = 'success';
$powerType = 'moderator';
}
@endphp
@if($isProtected)
<div class="alert alert-danger virtubrick-alert virtubrick-error-popup mt-3">This user is protected.</div>
@endif
@if($isPowerful)
<div @class([
'alert',
'alert-' . $powerColor,
'virtubrick-alert',
'virtubrick-error-popup',
'mt-3' => !$isProtected
])>
This user is a {{ $powerType }}.
</div>
@endif
<x-admin.user-admin-label label="Username">{{ $user->username }}</x-admin.user-admin-label>
<x-admin.user-admin-label label="Previous User Names"><b>TODO</b></x-admin.user-admin-label>
<x-admin.user-admin-label label="Moderation Status"><span class="text-success">OK (TODO)</span></x-admin.user-admin-label>
<x-admin.user-admin-label label="User Id">
<x-admin.user-search-input id="userid" definition="User ID" :value="$user->id" :nolabel=true />
</x-admin.user-admin-label>
<x-admin.user-admin-label label="Username">
<x-admin.user-search-input id="username" definition="Username" :value="$user->username" :nolabel=true />
</x-admin.user-admin-label>
<x-admin.user-admin-label label="Current Location">Website <b>TODO</b></x-admin.user-admin-label>
<div class="row py-2 border-top">
<div class="col-6">
<img src="{{ asset('/images/testing/avatar.png') }}" width="200" height="200" class="img-fluid vb-charimg" />
</div>
<div class="col-6">
<a href="#" class="text-decoration-none">User Homepage</a><br/>
<a href="#" class="text-decoration-none">Moderate User</a>
</div>
</div>
</div>
<h4 class="mt-3">Punishments</h4>
<div class="card">
<table class="table virtubrick-table">
<thead>
<tr>
<th scope="col">ID</th>
<th scope="col">Action</th>
<th scope="col">Moderator</th>
<th scope="col">Created</th>
<th scope="col">Expiration</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="col">1</th>
<th scope="col">1 day, perm, etc...</th>
<th scope="col">Joe</th>
<th scope="col">1/2/3 4:5 6</th>
<th scope="col">1/2/3 4:5 6</th>
</tr>
</tbody>
</table>
</div>
<h4 class="mt-3">Seller Ban</h4>
<div class="card me-2 p-3 pt-0 vb-admin-divider">
<x-admin.user-admin-label label="Current Ban">None (todo)</x-admin.user-admin-label>
<div class="form-check">
<input class="form-check-input" type="radio" name="seller-ban" id="seller-unban">
<label class="form-check-label" for="seller-unban">Unban</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="seller-ban" id="seller-1dayban">
<label class="form-check-label" for="seller-1dayban">1 Day</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="seller-ban" id="seller-3dayban">
<label class="form-check-label" for="seller-3dayban">3 Days</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="seller-ban" id="seller-permban">
<label class="form-check-label" for="seller-permban">Forever</label>
</div>
<label for="seller-note" class="form-label">Note:</label>
<textarea type="text" class="form-control" id="seller-note" placeholder="Seller ban note."></textarea>
<button type="submit" class="btn btn-danger mt-1">Submit Seller Ban</button>
</div>
</div>
<div class="col-4">
<div class="d-flex">
<h4>User Notes</h4>
<button class="btn btn-sm btn-primary ms-auto"><i class="fa-solid fa-circle-plus"></i> Add New</button>
</div>
<div class="card px-3 vb-admin-divider">
<div class="border-top py-2">
<span class="badge bg-primary">XlXi</span>
<span class="badge bg-secondary">12/28/2022 5:35 PM</span>
<span class="badge bg-secondary">#1</span>
<p class="my-1">Example note.</p>
</div>
<div class="border-top py-2">
<span class="badge bg-primary">XlXi</span>
<span class="badge bg-secondary">12/28/2022 5:35 PM</span>
<span class="badge bg-secondary">#1</span>
<p class="my-1">Example note.</p>
</div>
</div>
<h4 class="mt-3">Update User</h4>
<div class="card me-2 p-3 pt-0 vb-admin-divider">
@owner
<x-admin.user-admin-label label="User Name">todo put a form here to update</x-admin.user-admin-label>
@endowner
@admin
<x-admin.user-admin-label label="User Blurb"><a href="#" class="text-decoration-none">(scrub) (todo)</a></x-admin.user-admin-label>
@endadmin
@owner
<x-admin.user-admin-label label="Email">todo put a form here to update</x-admin.user-admin-label>
<x-admin.user-admin-label label="Password">todo put a form here to update</x-admin.user-admin-label>
@endowner
<x-admin.user-admin-label label="Facebook Url">Todo Link and disconnect/or Not Connected</x-admin.user-admin-label>
<x-admin.user-admin-label label="Twitter Url">Todo Link and disconnect/or Not Connected</x-admin.user-admin-label>
<x-admin.user-admin-label label="YouTube Url">Todo Link and disconnect/or Not Connected</x-admin.user-admin-label>
<x-admin.user-admin-label label="Twitch Url">Todo Link and disconnect/or Not Connected</x-admin.user-admin-label>
<x-admin.user-admin-label label="User Created">{{ $user->created_at->isoFormat('l LT') }}</x-admin.user-admin-label>
<x-admin.user-admin-label label="Last Location">Website (todo)</x-admin.user-admin-label>
<x-admin.user-admin-label label="User Activity">{{ $user->last_seen->isoFormat('l LT') }}</x-admin.user-admin-label>
@owner
<button type="submit" class="btn btn-warning">Update User</button>
@endowner
</div>
<h4 class="mt-3">Badges and Settings</h4>
<div class="card me-2 px-3 vb-admin-divider">
<x-admin.user-admin-label label="Tokens">{{ $user->tokens }}</x-admin.user-admin-label>
</div>
</div>
</div>
</div>
@endpush

View File

@ -0,0 +1,58 @@
@extends('layouts.admin')
@section('title', 'Lookup Tool')
@php
if(isset($users) && count($users) == 0)
$error = 'No users found.';
@endphp
@push('content')
<div class="container-md">
<x-admin.navigation.user-search />
<h4>Lookup Tool</h4>
@if(isset($error))
<div class="alert alert-danger virtubrick-alert virtubrick-error-popup">{{ $error }}</div>
@endif
<div class="card p-3">
<div class="row">
<div class="col-3">
<form method="POST" action="{{ route('admin.userlookup') }}" enctype="multipart/form-data">
@csrf
<textarea type="text" class="form-control" name="lookup" placeholder="Usernames, one per line." rows="10">{{ isset($input) ? $input : '' }}</textarea>
<button type="submit" class="btn btn-primary mt-1">Search</button>
</form>
</div>
@if(isset($users) && count($users) > 0)
<div class="col-3">
<table class="table virtubrick-table">
<thead>
<tr>
<th scope="col">User Id</th>
<th scope="col">User</th>
</tr>
</thead>
<tbody>
@foreach($users as $user)
<tr>
@if($user['found'])
<th scope="col">{{ $user['user']->id }}</th>
<th scope="col">
<a href="{{ route('admin.useradmin', ['ID' => $user['user']->id]) }}" class="text-decoration-none">
<x-user-circle :user="$user['user']" :size=37 />
</a>
</th>
@else
<th scope="col"></th>
<th scope="col">Unable to find {{ $user['username'] }}</th>
@endif
</tr>
@endforeach
</tbody>
</table>
</div>
@endif
</div>
</div>
</div>
@endpush

View File

@ -9,6 +9,7 @@
@push('content')
<div class="container-md">
<x-admin.navigation.user-search />
<h4>Find User</h4>
@if(isset($error))
<div class="alert alert-danger virtubrick-alert virtubrick-error-popup">{{ $error }}</div>
@ -49,8 +50,7 @@
@endphp
<tr class="align-middle">
<th scope="col">
{{-- TODO: XlXi: Switch to user admin page when it exists. --}}
<a href="{{ $user->getProfileUrl() }}" class="text-decoration-none">
<a href="{{ route('admin.useradmin', ['ID' => $user->id]) }}" class="text-decoration-none">
<x-user-circle :user="$user" :size=40 />
</a>
</th>

View File

@ -8,12 +8,7 @@
@section('quick-admin')
<li class="nav-item">
{{-- TODO: XlXi: Make this use route() --}}
<a href="{{ url('/admin') }}" class="nav-link py-0">User Admin</a>
</li>
<li class="nav-item">
{{-- TODO: XlXi: Make this use route() --}}
<a href="{{ url('/admin') }}" class="nav-link py-0">Moderate User</a>
<a href="{{ route('admin.useradmin', ['ID' => $user->id]) }}" class="nav-link py-0">User Admin</a>
</li>
@endsection

View File

@ -46,8 +46,15 @@ Route::group(['as' => 'admin.'], function() {
Route::group(['as' => 'admin.', 'prefix' => 'admin'], function() {
Route::middleware('roleset:moderator')->group(function () {
Route::get('/', 'AdminController@dashboard')->name('dashboard');
Route::get('/users', 'AdminController@userSearch')->name('usersearch');
Route::post('/users', 'AdminController@userSearchQuery')->name('usersearchquery');
Route::group(['prefix' => 'users'], function() {
Route::get('/useradmin', 'AdminController@userAdmin')->name('useradmin');
Route::get('/find', 'AdminController@userSearch')->name('usersearch');
Route::post('/find', 'AdminController@userSearchQuery')->name('usersearchquery');
Route::get('/userlookuptool', 'AdminController@userLookup')->name('userlookup');
Route::post('/userlookuptool', 'AdminController@userLookupQuery')->name('userlookupquery');
});
Route::get('/auto-uploader', 'AdminController@autoUpload')->name('autoupload');
});

View File

@ -14,7 +14,10 @@
<category name="Groups">
<link name="Group Admin" route="admin.dashboard" />
<link name="Group Mass Reward" route="admin.dashboard" />
</category>
<category name="Inventory Management">
<link name="Collectibles Audit" route="admin.dashboard" />
</category>
<category name="Private Servers">
@ -57,11 +60,14 @@
<link name="Mass Password Reset" route="admin.dashboard" />
</category>
<category name="Groups">
<link name="Group Mass Reward" route="admin.dashboard" />
</category>
<category name="Inventory Management">
<link name="Asset Scrub" route="admin.dashboard" />
<link name="Game Pass Scrub" route="admin.dashboard" />
<link name="Badge Scrub" route="admin.dashboard" />
<link name="Collectibles Audit" route="admin.dashboard" />
</category>
<category name="Universe Status">