Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Admin Overview page with counters and statistics #23

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions app/Classes/Pterodactyl/PterodactylClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@ public function getNests(): PromiseInterface|Response
return $this->handleResponse($response);
}

/**
* @return mixed
* @throws Exception
* @description Returns the infos of a single node
*/
public function getNode($id) {
$response = $this->client->get('application/nodes/' . $id);
return $this->handleResponse($response);
}

/**
* @throws PterodactylRequestException
*/
Expand Down Expand Up @@ -178,6 +188,18 @@ public function updateServerDetails(int $pterodactyl_id, array $data): PromiseIn
return $this->handleResponse($response);
}

/**
* Get Servers from Pterodactyl
*
* @return PromiseInterface|Response
* @throws PterodactylRequestException
*/
public function getServers(): PromiseInterface|Response
{
$response = $this->client->get('application/servers?per_page=' . self::PER_PAGE);
return $this->handleResponse($response);
}

/**
* Delete server
*
Expand Down
130 changes: 130 additions & 0 deletions app/Http/Controllers/Admin/OverviewController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<?php

namespace App\Http\Controllers\Admin;

use App\Classes\Pterodactyl\PterodactylClient;
use App\Http\Controllers\Controller;
use App\Models\Pterodactyl\Egg;
use App\Models\Pterodactyl\Location;
use App\Models\Pterodactyl\Nest;
use App\Models\Pterodactyl\Node;
use App\Models\Server;
use App\Models\User;
use App\Settings\GeneralSettings;
use App\Settings\PterodactylSettings;

class OverviewController extends Controller
{
public const TTL = 86400;

public function index(GeneralSettings $settings)
{

//Check the last Sync Status
$lastEgg = Egg::query()->latest('updated_at')->first();
$syncLastUpdate = $lastEgg ? $lastEgg->updated_at->isoFormat('LLL') : __('unknown');

//Generate the Counters used on the Overview
$counters = $this->constructNodeInfo()["counters"];
$nodes = $this->constructNodeInfo()["nodes"];

return view('admin.overview.index', [
'counters' => $counters,
'nodeCounters' => $nodes,
'lastPteroSync' => $syncLastUpdate,
'settings' => $settings
]);


}


/**
* @return \Illuminate\Support\Collection
*/
private function constructCounters()
{

$userCount = User::query()->count();
$creditCount = User::query()->sum('credits');
$creditCount = number_format($creditCount, 2, '.', '');
//Get counters
$counters = collect();
//Set basic variables in the collection
$counters->put('users', $userCount);
$counters->put('credits', $creditCount);
// $counters->put('payments', Payment::all()->count());
$counters->put('eggs', Egg::all()->count());
$counters->put('nests', Nest::all()->count());
$counters->put('locations', Location::all()->count());

//Prepare for counting
$counters->put('servers', collect());
$counters['servers']->active = 0;
$counters['servers']->total = 0;
$counters->put('earnings', collect());
$counters['earnings']->active = 0;
$counters['earnings']->total = 0;
$counters->put('totalUsagePercent', 0);

return $counters;
}


/**
* @return array
*/
private function constructNodeInfo()
{
$pterosettings = new PterodactylSettings();
$pterodactylClient = new PterodactylClient($pterosettings);
$counters = $this->constructCounters();

//Get node information
$nodes = collect();
foreach ($DBnodes = Node::all() as $DBnode) { //gets all node information and prepares the structure
$nodeId = $DBnode['id'];
$nodes->put($nodeId, collect());
$nodes[$nodeId]->id = $nodeId;
$nodes[$nodeId]->name = $DBnode['name'];
$pteroNode = $pterodactylClient->getNode($nodeId)->json()["attributes"];
$nodes[$nodeId]->usagePercent = round(max($pteroNode['allocated_resources']['memory'] / ($pteroNode['memory'] * ($pteroNode['memory_overallocate'] + 100) / 100), $pteroNode['allocated_resources']['disk'] / ($pteroNode['disk'] * ($pteroNode['disk_overallocate'] + 100) / 100)) * 100, 2);
$counters['totalUsagePercent'] += $nodes[$nodeId]->usagePercent;

$nodes[$nodeId]->totalServers = 0;
$nodes[$nodeId]->activeServers = 0;
$nodes[$nodeId]->totalEarnings = 0;
$nodes[$nodeId]->activeEarnings = 0;
}
$counters['totalUsagePercent'] = ($DBnodes->count()) ? round($counters['totalUsagePercent'] / $DBnodes->count(), 2) : 0;

$response = ($pterodactylClient->getServers()->json());
foreach ($response["data"] as $server) { //gets all servers from Pterodactyl and calculates total of credit usage for each node separately + total

$serverId = $server['attributes']['id'];
$nodeId = $server['attributes']['node'];

$CPServer = Server::query()->where('pterodactyl_id', $serverId)->first();

if ($CPServer) {
$price = Server::query()->where('id', $CPServer->product_id)->first()->price;
if (!$CPServer->suspended) {
$counters['earnings']->active += $price;
$counters['servers']->active++;
$nodes[$nodeId]->activeEarnings += $price;
$nodes[$nodeId]->activeServers++;
}
$counters['earnings']->total += $price;
$counters['servers']->total++;
$nodes[$nodeId]->totalEarnings += $price;
$nodes[$nodeId]->totalServers++;
}
}
return [
'counters' => $counters,
'nodes' => $nodes
];
}


}
156 changes: 156 additions & 0 deletions resources/views/admin/overview/index.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
@extends('layouts.dashboard')

@section('content')
<div class="main py-4">
<!-- Cards -->
<div class="row">
<!-- Server card -->
<div class="col-xl-3 col-sm-6 col-12">
<div class="card mb-3">
<div class="card-body">
<div class="row">
<div class="col">
<span class="h6 font-semibold text-muted text-sm d-block mb-2">Total servers</span>
<span class="h4 font-bold mb-0">{{$counters['servers']->total}}</span>
</div>
<div class="col-auto">
<div class="icon icon-shape bg-primary text-white text-lg rounded-circle">
<i class="fas fa-server fa-2x"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- /Server card -->

<!-- Credits card -->
<div class="col-xl-3 col-sm-6 col-12">
<div class="card mb-3">
<div class="card-body">
<div class="row">
<div class="col">
<span
class="h6 font-semibold text-muted text-sm d-block mb-2">{{__("Total users")}}</span>
<span class="h4 font-bold mb-0">{{$counters['users']}}</span>
</div>
<div class="col-auto">
<div class="icon icon-shape bg-success text-white text-lg rounded-circle">
<i class="fas fa-coins fa-2x"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- /Credits card -->

<!-- Usage card -->
<div class="col-xl-3 col-sm-6 col-12">
<div class="card mb-3">
<div class="card-body">
<div class="row">
<div class="col">
<span class="h6 font-semibold text-muted text-sm d-block mb-2">{{__('Total :credits ', ['credits' => $settings->credits_display_name])}}
</span>
<span class="h4 font-bold mb-0">{{$counters['credits']}}</span>
</div>
<div class="col-auto">
<div class="icon icon-shape bg-warning text-white text-lg rounded-circle">
<i class="fas fa-chart-line fa-2x"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- /Usage card -->

<!-- Usage card -->
<div class="col-xl-3 col-sm-6 col-12">
<div class="card mb-3">
<div class="card-body">
<div class="row">
<div class="col">
<span class="h6 font-semibold text-muted text-sm d-block mb-2">{{__('Total :credits earnings', ['credits' => $settings->credits_display_name])}}
</span>
<span class="h4 font-bold mb-0">{{$counters['earnings']->active}}</span>
</div>
<div class="col-auto">
<div class="icon icon-shape bg-warning text-white text-lg rounded-circle">
<i class="fas fa-chart-line fa-2x"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- /Usage card -->
<!-- Usage card -->
<div class="col-xl-3 col-sm-6 col-12">
<div class="card mb-3">
<div class="card-body">
<div class="row">
<div class="col">
<span class="h6 font-semibold text-muted text-sm d-block mb-2">{{__('Last Sync from Pterodactyl')}}
</span>
<span class="h4 font-bold mb-0">{{$lastPteroSync}}</span>
</div>
<div class="col-auto">
<div class="icon icon-shape bg-warning text-white text-lg rounded-circle">
<i class="fas fa-upload fa-2x"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- /Usage card -->

<!-- Usage card -->
<div class=" col-sm-6">
<div class="card mb-3">
<div class="card-body">
<span class="h4 font-bold mb-0">{{__("Nodes Info")}}</span>
<div class="table-responsive">
<table class="table table-striped table-hover table-sm ">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Usage-%</th>
<th scope="col">Total Servers</th>
<th scope="col">Active Servers</th>
<th scope="col">Active Earnings</th>
</tr>
</thead>
<tbody>

@foreach($nodeCounters as $node)

<tr>
<th scope="row">{{$node->id}}</th>
<td>{{$node->name}}</td>
<td>{{$node->usagePercent}}</td>
<td>{{$node->totalServers}}</td>
<td>{{$node->activeServers}}</td>
<td>{{$node->activeEarnings}}</td>
</tr>
@endforeach

</tbody>

</table>
</div>
</div>
</div>
</div>
<!-- /Usage card -->
</div>
<!-- /Cards -->


</div>
@endsection


Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@can('admin.overview.read')
<li class="nav-item {{ request()->routeIs('admin.overview.index') ? 'active' : '' }}">
<a href="{{ route('admin.overview.index') }}" class="nav-link">
<span class="sidebar-icon me-3">
<i class="fas fa-eye fa-fw"></i>
</span>
<span class="sidebar-text">{{ __('Overview') }}</span>
</a>
</li>
@endcan
2 changes: 2 additions & 0 deletions routes/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use App\Http\Controllers\Admin\ConfigurationController;
use App\Http\Controllers\Admin\NotificationTemplateController;
use App\Http\Controllers\Admin\OverviewController;
use App\Http\Controllers\Admin\RoleController;
use App\Http\Controllers\Admin\ServerController as AdminServerController;
use App\Http\Controllers\Admin\UserController;
Expand Down Expand Up @@ -55,6 +56,7 @@
Route::resource('roles', RoleController::class);
Route::resource('users', UserController::class);
Route::resource('servers', AdminServerController::class);
Route::get('overview', [OverviewController::class, 'index'])->name('overview.index');
Route::get('/configurations/sync', [ConfigurationController::class, 'syncPterodactyl'])->name('configurations.sync');
Route::get('/configurations/{configuration}/clone', [ConfigurationController::class, 'clone'])->name('configurations.clone');
Route::resource('configurations', ConfigurationController::class);
Expand Down