Kaynağa Gözat

removed pterodactyl pages, added overview page, made server create work

AVMG20 3 yıl önce
ebeveyn
işleme
24e6d48496

+ 55 - 57
app/Classes/Pterodactyl.php

@@ -11,66 +11,40 @@ use Exception;
 use Illuminate\Http\Client\PendingRequest;
 use Illuminate\Http\Client\Response;
 use Illuminate\Support\Facades\Http;
-use Illuminate\Validation\Validator;
 
 class Pterodactyl
 {
     /**
-     * @return PendingRequest
+     * @return null
+     * @throws Exception
      */
-    public static function client()
+    public static function getNests()
     {
-        return Http::withHeaders([
-            'Authorization' => 'Bearer ' . env('PTERODACTYL_TOKEN', false),
-            'Content-type'  => 'application/json',
-            'Accept'        => 'Application/vnd.pterodactyl.v1+json',
-        ])->baseUrl(env('PTERODACTYL_URL') . '/api');
+        $response = self::client()->get('/application/nests');
+        if ($response->failed()) throw self::getException();
+        return $response->json()['data'];
     }
 
     //TODO: Extend error handling (maybe logger for more errors when debugging)
-    /**
-     * Get user by pterodactyl id
-     * @param int $pterodactylId
-     * @return mixed
-     */
-    public function getUser(int $pterodactylId)
-    {
-        $response = self::client()->get("/application/users/{$pterodactylId}");
-
-        if ($response->failed()) return $response->json();
-        return $response->json()['attributes'];
-    }
 
     /**
-     * @param Node $node
-     * @return array|mixed|null
-     * @throws Exception
+     * @return PendingRequest
      */
-    public static function getFreeAllocations(Node $node)
+    public static function client()
     {
-        $response = self::getAllocations($node);
-        $freeAllocations = [];
-
-        if (isset($response['data'])) {
-            if (!empty($response['data'])) {
-                foreach ($response['data'] as $allocation) {
-                    if (!$allocation['attributes']['assigned']) array_push($freeAllocations, $allocation);
-                }
-            }
-        }
-
-        return $freeAllocations;
+        return Http::withHeaders([
+            'Authorization' => 'Bearer ' . env('PTERODACTYL_TOKEN', false),
+            'Content-type'  => 'application/json',
+            'Accept'        => 'Application/vnd.pterodactyl.v1+json',
+        ])->baseUrl(env('PTERODACTYL_URL') . '/api');
     }
 
     /**
-     * @return null
-     * @throws Exception
+     * @return Exception
      */
-    public static function getNests()
+    private static function getException(): Exception
     {
-        $response = self::client()->get('/application/nests');
-        if ($response->failed()) throw self::getException();
-        return $response->json()['data'];
+        return new Exception('Request Failed, is pterodactyl set-up correctly?');
     }
 
     /**
@@ -85,7 +59,6 @@ class Pterodactyl
         return $response->json()['data'];
     }
 
-
     /**
      * @return mixed
      * @throws Exception
@@ -94,10 +67,10 @@ class Pterodactyl
     {
         $response = self::client()->get('/application/nodes');
         if ($response->failed()) throw self::getException();
+        dd($response->json());
         return $response->json()['data'];
     }
 
-
     /**
      * @return mixed
      * @throws Exception
@@ -119,6 +92,26 @@ class Pterodactyl
         return self::getFreeAllocations($node)[0]['attributes']['id'] ?? null;
     }
 
+    /**
+     * @param Node $node
+     * @return array|mixed|null
+     * @throws Exception
+     */
+    public static function getFreeAllocations(Node $node)
+    {
+        $response = self::getAllocations($node);
+        $freeAllocations = [];
+
+        if (isset($response['data'])) {
+            if (!empty($response['data'])) {
+                foreach ($response['data'] as $allocation) {
+                    if (!$allocation['attributes']['assigned']) array_push($freeAllocations, $allocation);
+                }
+            }
+        }
+
+        return $freeAllocations;
+    }
 
     /**
      * @param Node $node
@@ -132,7 +125,6 @@ class Pterodactyl
         return $response->json();
     }
 
-
     /**
      * @param String $route
      * @return string
@@ -151,21 +143,21 @@ class Pterodactyl
     public static function createServer(Server $server, Egg $egg, int $allocationId)
     {
         return self::client()->post("/application/servers", [
-            "name"           => $server->name,
-            "external_id"    => $server->id,
-            "user"           => $server->user->pterodactyl_id,
-            "egg"            => $egg->id,
-            "docker_image"   => $egg->docker_image,
-            "startup"        => $egg->startup,
-            "environment"    => $egg->getEnvironmentVariables(),
-            "limits"         => [
+            "name"                => $server->name,
+            "external_id"         => $server->id,
+            "user"                => $server->user->pterodactyl_id,
+            "egg"                 => $egg->id,
+            "docker_image"        => $egg->docker_image,
+            "startup"             => $egg->startup,
+            "environment"         => $egg->getEnvironmentVariables(),
+            "limits"              => [
                 "memory" => $server->product->memory,
                 "swap"   => $server->product->swap,
                 "disk"   => $server->product->disk,
                 "io"     => $server->product->io,
                 "cpu"    => $server->product->cpu
             ],
-            "feature_limits" => [
+            "feature_limits"      => [
                 "databases"   => $server->product->databases,
                 "backups"     => $server->product->backups,
                 "allocations" => $server->product->allocations,
@@ -174,6 +166,7 @@ class Pterodactyl
                 "default" => $allocationId
             ]
         ]);
+
     }
 
     public static function suspendServer(Server $server)
@@ -191,10 +184,15 @@ class Pterodactyl
     }
 
     /**
-     * @return Exception
+     * Get user by pterodactyl id
+     * @param int $pterodactylId
+     * @return mixed
      */
-    private static function getException(): Exception
+    public function getUser(int $pterodactylId)
     {
-        return new Exception('Request Failed, is pterodactyl set-up correctly?');
+        $response = self::client()->get("/application/users/{$pterodactylId}");
+
+        if ($response->failed()) return $response->json();
+        return $response->json()['attributes'];
     }
 }

+ 5 - 0
app/Http/Controllers/Admin/NestsController.php

@@ -14,6 +14,11 @@ use Illuminate\Http\RedirectResponse;
 use Illuminate\Http\Request;
 use Illuminate\Http\Response;
 
+/**
+ * @deprecated
+ * Class NestsController
+ * @package App\Http\Controllers\Admin
+ */
 class NestsController extends Controller
 {
     /**

+ 5 - 0
app/Http/Controllers/Admin/NodeController.php

@@ -14,6 +14,11 @@ use Illuminate\Http\RedirectResponse;
 use Illuminate\Http\Request;
 use Illuminate\Http\Response;
 
+/**
+ * @deprecated
+ * Class NodeController
+ * @package App\Http\Controllers\Admin
+ */
 class NodeController extends Controller
 {
     /**

+ 40 - 0
app/Http/Controllers/Admin/OverViewController.php

@@ -0,0 +1,40 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Http\Controllers\Controller;
+use App\Models\Payment;
+use App\Models\Server;
+use App\Models\User;
+use Illuminate\Support\Facades\Cache;
+
+class OverViewController extends Controller
+{
+    public const TTL = 86400;
+
+    public function index()
+    {
+        $userCount = Cache::remember('user:count', self::TTL, function () {
+            return User::query()->count();
+        });
+
+        $creditCount = Cache::remember('credit:count', self::TTL, function () {
+            return User::query()->sum('credits');
+        });
+
+        $paymentCount = Cache::remember('payment:count', self::TTL, function () {
+            return Payment::query()->count();
+        });
+
+        $serverCount = Cache::remember('server:count', self::TTL, function () {
+            return Server::query()->count();
+        });
+
+        return view('admin.overview.index', [
+            'serverCount'  => $serverCount,
+            'userCount'    => $userCount,
+            'paymentCount' => $paymentCount,
+            'creditCount'  => number_format($creditCount, 2, '.', ''),
+        ]);
+    }
+}

+ 15 - 12
app/Http/Controllers/ServerController.php

@@ -52,8 +52,8 @@ class ServerController extends Controller
         }
 
         // minimum credits
-        if (FacadesRequest::has("product_id")) {
-            $product = Product::findOrFail(FacadesRequest::input("product_id"));
+        if (FacadesRequest::has("product")) {
+            $product = Product::findOrFail(FacadesRequest::input("product"));
             if (
                 Auth::user()->credits <
                 ($product->minimum_credits == -1
@@ -83,17 +83,20 @@ class ServerController extends Controller
         if (!is_null($this->validateConfigurationRules())) return $this->validateConfigurationRules();
 
         $request->validate([
-            "name"        => "required|max:191",
-            "description" => "nullable|max:191",
-            "node_id"     => "required|exists:nodes,id",
-            "egg_id"      => "required|exists:eggs,id",
-            "product_id"  => "required|exists:products,id"
+            "name"    => "required|max:191",
+            "node"    => "required|exists:nodes,id",
+            "egg"     => "required|exists:eggs,id",
+            "product" => "required|exists:products,id"
         ]);
 
         //get required resources
-        $egg = Egg::findOrFail($request->input('egg_id'));
-        $node = Node::findOrFail($request->input('node_id'));
-        $server = Auth::user()->servers()->create($request->all());
+        $egg = Egg::findOrFail($request->input('egg'));
+        $node = Node::findOrFail($request->input('node'));
+
+        $server = $request->user()->servers()->create([
+            'name'       => $request->input('name'),
+            'product_id' => $request->input('product'),
+        ]);
 
         //get free allocation ID
         $allocationId = Pterodactyl::getFreeAllocationId($node);
@@ -110,8 +113,8 @@ class ServerController extends Controller
         ]);
 
         if (Configuration::getValueByKey('SERVER_CREATE_CHARGE_FIRST_HOUR', 'true') == 'true') {
-            if (Auth::user()->credits >= $server->product->getHourlyPrice()) {
-                Auth::user()->decrement('credits', $server->product->getHourlyPrice());
+            if ($request->user()->credits >= $server->product->getHourlyPrice()) {
+                $request->user()->decrement('credits', $server->product->getHourlyPrice());
             }
         }
 

+ 0 - 1
app/Models/Node.php

@@ -33,7 +33,6 @@ class Node extends Model
         Location::syncLocations();
         $nodes = Pterodactyl::getNodes();
 
-
         $nodes = array_map(function($node) {
             return array(
                 'id' => $node['attributes']['id'],

+ 1 - 0
composer.json

@@ -28,6 +28,7 @@
         "yajra/laravel-datatables-oracle": "~9.0"
     },
     "require-dev": {
+        "barryvdh/laravel-debugbar": "^3.6",
         "facade/ignition": "^2.5",
         "fakerphp/faker": "^1.9.1",
         "laravel/sail": "^1.0.1",

+ 220 - 2
composer.lock

@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "f7ba581ff6641d3ab79d558070e99f3c",
+    "content-hash": "500346cc4a4a83b162e07bb0071d1602",
     "packages": [
         {
             "name": "asm89/stack-cors",
@@ -6202,6 +6202,91 @@
         }
     ],
     "packages-dev": [
+        {
+            "name": "barryvdh/laravel-debugbar",
+            "version": "v3.6.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/barryvdh/laravel-debugbar.git",
+                "reference": "3c2d678269ba60e178bcd93e36f6a91c36b727f1"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/3c2d678269ba60e178bcd93e36f6a91c36b727f1",
+                "reference": "3c2d678269ba60e178bcd93e36f6a91c36b727f1",
+                "shasum": ""
+            },
+            "require": {
+                "illuminate/routing": "^6|^7|^8",
+                "illuminate/session": "^6|^7|^8",
+                "illuminate/support": "^6|^7|^8",
+                "maximebf/debugbar": "^1.17.2",
+                "php": ">=7.2",
+                "symfony/debug": "^4.3|^5",
+                "symfony/finder": "^4.3|^5"
+            },
+            "require-dev": {
+                "mockery/mockery": "^1.3.3",
+                "orchestra/testbench-dusk": "^4|^5|^6",
+                "phpunit/phpunit": "^8.5|^9.0",
+                "squizlabs/php_codesniffer": "^3.5"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.6-dev"
+                },
+                "laravel": {
+                    "providers": [
+                        "Barryvdh\\Debugbar\\ServiceProvider"
+                    ],
+                    "aliases": {
+                        "Debugbar": "Barryvdh\\Debugbar\\Facade"
+                    }
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Barryvdh\\Debugbar\\": "src/"
+                },
+                "files": [
+                    "src/helpers.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Barry vd. Heuvel",
+                    "email": "barryvdh@gmail.com"
+                }
+            ],
+            "description": "PHP Debugbar integration for Laravel",
+            "keywords": [
+                "debug",
+                "debugbar",
+                "laravel",
+                "profiler",
+                "webprofiler"
+            ],
+            "support": {
+                "issues": "https://github.com/barryvdh/laravel-debugbar/issues",
+                "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.6.4"
+            },
+            "funding": [
+                {
+                    "url": "https://fruitcake.nl",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/barryvdh",
+                    "type": "github"
+                }
+            ],
+            "time": "2021-10-21T10:57:31+00:00"
+        },
         {
             "name": "doctrine/instantiator",
             "version": "1.4.0",
@@ -6713,6 +6798,71 @@
             },
             "time": "2021-05-25T16:41:13+00:00"
         },
+        {
+            "name": "maximebf/debugbar",
+            "version": "v1.17.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/maximebf/php-debugbar.git",
+                "reference": "e8ac3499af0ea5b440908e06cc0abe5898008b3c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/e8ac3499af0ea5b440908e06cc0abe5898008b3c",
+                "reference": "e8ac3499af0ea5b440908e06cc0abe5898008b3c",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1|^8",
+                "psr/log": "^1|^2|^3",
+                "symfony/var-dumper": "^2.6|^3|^4|^5"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^7.5.20 || ^9.4.2"
+            },
+            "suggest": {
+                "kriswallsmith/assetic": "The best way to manage assets",
+                "monolog/monolog": "Log using Monolog",
+                "predis/predis": "Redis storage"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.17-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "DebugBar\\": "src/DebugBar/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Maxime Bouroumeau-Fuseau",
+                    "email": "maxime.bouroumeau@gmail.com",
+                    "homepage": "http://maximebf.com"
+                },
+                {
+                    "name": "Barry vd. Heuvel",
+                    "email": "barryvdh@gmail.com"
+                }
+            ],
+            "description": "Debug bar in the browser for php application",
+            "homepage": "https://github.com/maximebf/php-debugbar",
+            "keywords": [
+                "debug",
+                "debugbar"
+            ],
+            "support": {
+                "issues": "https://github.com/maximebf/php-debugbar/issues",
+                "source": "https://github.com/maximebf/php-debugbar/tree/v1.17.3"
+            },
+            "time": "2021-10-19T12:33:27+00:00"
+        },
         {
             "name": "mockery/mockery",
             "version": "1.4.3",
@@ -8652,6 +8802,74 @@
             ],
             "time": "2020-09-28T06:39:44+00:00"
         },
+        {
+            "name": "symfony/debug",
+            "version": "v4.4.31",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/debug.git",
+                "reference": "43ede438d4cb52cd589ae5dc070e9323866ba8e0"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/debug/zipball/43ede438d4cb52cd589ae5dc070e9323866ba8e0",
+                "reference": "43ede438d4cb52cd589ae5dc070e9323866ba8e0",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1.3",
+                "psr/log": "^1|^2|^3"
+            },
+            "conflict": {
+                "symfony/http-kernel": "<3.4"
+            },
+            "require-dev": {
+                "symfony/http-kernel": "^3.4|^4.0|^5.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Debug\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Provides tools to ease debugging PHP code",
+            "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/debug/tree/v4.4.31"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2021-09-24T13:30:14+00:00"
+        },
         {
             "name": "theseer/tokenizer",
             "version": "1.2.0",
@@ -8713,5 +8931,5 @@
         "ext-intl": "*"
     },
     "platform-dev": [],
-    "plugin-api-version": "2.1.0"
+    "plugin-api-version": "2.0.0"
 }

+ 147 - 0
resources/views/admin/overview/index.blade.php

@@ -0,0 +1,147 @@
+@extends('layouts.main')
+
+@section('content')
+    <!-- CONTENT HEADER -->
+    <section class="content-header">
+        <div class="container-fluid">
+            <div class="row mb-2">
+                <div class="col-sm-6">
+                    <h1>Admin Overview</h1>
+                </div>
+                <div class="col-sm-6">
+                    <ol class="breadcrumb float-sm-right">
+                        <li class="breadcrumb-item"><a href="{{route('home')}}">Dashboard</a></li>
+                        <li class="breadcrumb-item"><a class="text-muted"
+                                                       href="{{route('admin.overview.index')}}">Admin Overview</a></li>
+                    </ol>
+                </div>
+            </div>
+        </div>
+    </section>
+    <!-- END CONTENT HEADER -->
+
+    <!-- MAIN CONTENT -->
+    <section class="content">
+        <div class="container-fluid">
+
+            <div class="row mb-3">
+                <div class="col-md-3">
+                    <a href="https://discord.gg/4Y6HjD2uyU" class="btn btn-dark btn-block px-3"><i class="fab fa-discord mr-2"></i> {{__('Support server')}}</a>
+                </div>
+                <div class="col-md-3">
+                    <a href="https://controlpanel.gg/docs/intro" class="btn btn-dark btn-block px-3"><i class="fas fa-link mr-2"></i> {{__('Documentation')}}</a>
+                </div>
+                <div class="col-md-3">
+                    <a href="https://github.com/ControlPanel-gg/dashboard" class="btn btn-dark btn-block px-3"><i class="fab fa-github mr-2"></i> {{__('Github')}}</a>
+                </div>
+                <div class="col-md-3">
+                    <a href="https://controlpanel.gg/docs/Contributing/donating" class="btn btn-dark btn-block px-3"><i class="fas fa-money-bill mr-2"></i> {{__('Support ControlPanel')}}</a>
+                </div>
+            </div>
+
+            <div class="row">
+                <div class="col-12 col-sm-6 col-md-3">
+                    <div class="info-box">
+                        <span class="info-box-icon bg-info elevation-1"><i class="fas fa-server"></i></span>
+
+                        <div class="info-box-content">
+                            <span class="info-box-text">{{__('Servers')}}</span>
+                            <span class="info-box-number">{{$serverCount}}</span>
+                        </div>
+                        <!-- /.info-box-content -->
+                    </div>
+                    <!-- /.info-box -->
+                </div>
+
+                <div class="col-12 col-sm-6 col-md-3">
+                    <div class="info-box">
+                        <span class="info-box-icon bg-primary elevation-1"><i class="fas fa-users"></i></span>
+
+                        <div class="info-box-content">
+                            <span class="info-box-text">{{__('Users')}}</span>
+                            <span class="info-box-number">{{$userCount}}</span>
+                        </div>
+                        <!-- /.info-box-content -->
+                    </div>
+                    <!-- /.info-box -->
+                </div>
+
+                <div class="col-12 col-sm-6 col-md-3">
+                    <div class="info-box">
+                        <span class="info-box-icon bg-warning elevation-1"><i class="fas fa-coins text-white"></i></span>
+
+                        <div class="info-box-content">
+                            <span class="info-box-text">{{__('Total')}} {{CREDITS_DISPLAY_NAME}}</span>
+                            <span class="info-box-number">{{$creditCount}}</span>
+                        </div>
+                        <!-- /.info-box-content -->
+                    </div>
+                    <!-- /.info-box -->
+                </div>
+
+                <div class="col-12 col-sm-6 col-md-3">
+                    <div class="info-box">
+                        <span class="info-box-icon bg-success elevation-1"><i class="fas fa-money-bill"></i></span>
+
+                        <div class="info-box-content">
+                            <span class="info-box-text">{{__('Payments')}}</span>
+                            <span class="info-box-number">{{$paymentCount}}</span>
+                        </div>
+                        <!-- /.info-box-content -->
+                    </div>
+                    <!-- /.info-box -->
+                </div>
+            </div>
+
+            <div class="row">
+                <div class="col-md-6">
+                    <div class="card">
+                        <div class="card-header">
+                            <div class="d-flex justify-content-between">
+                                <div class="card-title ">
+                                    <span><i class="fas fa-kiwi-bird mr-2"></i>{{__('Pterodactyl')}}</span>
+                                </div>
+                                <button class="btn btn-primary"><i class="fas fa-sync mr-2"></i>{{__('Sync')}}</button>
+                            </div>
+                        </div>
+                        <div class="card-body py-1">
+                            <table class="table">
+                               <thead>
+                               <tr>
+                                   <th>{{__('Resources')}}</th>
+                                   <th>{{__('Count')}}</th>
+                               </tr>
+                               </thead>
+                                <tbody>
+                                <tr>
+                                    <td>{{__('Locations')}}</td>
+                                    <td>1</td>
+                                </tr>
+                                <tr>
+                                    <td>{{__('Nodes')}}</td>
+                                    <td>1</td>
+                                </tr>
+                                <tr>
+                                    <td>{{__('Nests')}}</td>
+                                    <td>1</td>
+                                </tr>
+                                <tr>
+                                    <td>{{__('Eggs')}}</td>
+                                    <td>1</td>
+                                </tr>
+                                </tbody>
+                            </table>
+                        </div>
+                        <div class="card-footer">
+                            <span><i class="fas fa-sync mr-2"></i>{{__('Last updated :date', ['date' => now()])}}</span>
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+        </div>
+        <!-- END CUSTOM CONTENT -->
+
+    </section>
+    <!-- END CONTENT -->
+@endsection

+ 23 - 13
resources/views/admin/products/create.blade.php

@@ -50,7 +50,8 @@
                                     <div class="col-lg-6">
                                         <div class="form-group">
                                             <label for="name">Name</label>
-                                            <input value="{{$product->name ?? old('name')}}" id="name" name="name" type="text"
+                                            <input value="{{$product->name ?? old('name')}}" id="name" name="name"
+                                                   type="text"
                                                    class="form-control @error('name') is-invalid @enderror"
                                                    required="required">
                                             @error('name')
@@ -76,7 +77,8 @@
 
                                         <div class="form-group">
                                             <label for="memory">Memory</label>
-                                            <input value="{{$product->memory ?? old('memory')}}" id="memory" name="memory"
+                                            <input value="{{$product->memory ?? old('memory')}}" id="memory"
+                                                   name="memory"
                                                    type="number"
                                                    class="form-control @error('memory') is-invalid @enderror"
                                                    required="required">
@@ -133,7 +135,8 @@
                                     <div class="col-lg-6">
                                         <div class="form-group">
                                             <label for="disk">Disk</label>
-                                            <input value="{{$product->disk ?? old('disk') ?? 1000}}" id="disk" name="disk"
+                                            <input value="{{$product->disk ?? old('disk') ?? 1000}}" id="disk"
+                                                   name="disk"
                                                    type="number"
                                                    class="form-control @error('disk') is-invalid @enderror"
                                                    required="required">
@@ -174,7 +177,8 @@
                                         </div>
                                         <div class="form-group">
                                             <label for="databases">Databases</label>
-                                            <input value="{{$product->databases ?? old('databases') ?? 1}}" id="databases"
+                                            <input value="{{$product->databases ?? old('databases') ?? 1}}"
+                                                   id="databases"
                                                    name="databases"
                                                    type="number"
                                                    class="form-control @error('databases') is-invalid @enderror"
@@ -227,21 +231,25 @@
                     <div class="col-lg-6">
                         <div class="card">
                             <div class="card-header">
-                                <h5 class="card-title">Product Linking <i data-toggle="popover" data-trigger="hover"
-                                                                          data-content="Linked products will only be available when the user has selected the linked node and/or egg"
-                                                                          class="fas fa-info-circle"></i></h5>
+                                <h5 class="card-title">Product Linking
+                                    <i data-toggle="popover"
+                                       data-trigger="hover"
+                                       data-content="Link your products to nodes and eggs to create dynamic pricing for each option"
+                                       class="fas fa-info-circle"></i></h5>
                             </div>
                             <div class="card-body">
 
                                 <div class="form-group">
                                     <label for="nodes">Nodes</label>
-                                    <select id="nodes" style="width:100%" class="custom-select @error('nodes') is-invalid @enderror"
+                                    <select id="nodes" style="width:100%"
+                                            class="custom-select @error('nodes') is-invalid @enderror"
                                             name="nodes[]" multiple="multiple" autocomplete="off">
                                         @foreach($locations as $location)
                                             <optgroup label="{{$location->name}}">
                                                 @foreach($location->nodes as $node)
-                                                    <option @if(isset($product)) @if($product->nodes->contains('id' , $node->id)) selected
-                                                            @endif @endif value="{{$node->id}}">{{$node->name}}</option>
+                                                    <option
+                                                        @if(isset($product)) @if($product->nodes->contains('id' , $node->id)) selected
+                                                        @endif @endif value="{{$node->id}}">{{$node->name}}</option>
                                                 @endforeach
                                             </optgroup>
                                         @endforeach
@@ -259,13 +267,15 @@
 
                                 <div class="form-group">
                                     <label for="eggs">Eggs</label>
-                                    <select id="eggs" style="width:100%" class="custom-select @error('eggs') is-invalid @enderror"
+                                    <select id="eggs" style="width:100%"
+                                            class="custom-select @error('eggs') is-invalid @enderror"
                                             name="eggs[]" multiple="multiple" autocomplete="off">
                                         @foreach($nests as $nest)
                                             <optgroup label="{{$nest->name}}">
                                                 @foreach($nest->eggs as $egg)
-                                                    <option @if(isset($product)) @if($product->eggs->contains('id' , $egg->id)) selected
-                                                            @endif @endif  value="{{$egg->id}}">{{$egg->name}}</option>
+                                                    <option
+                                                        @if(isset($product)) @if($product->eggs->contains('id' , $egg->id)) selected
+                                                        @endif @endif  value="{{$egg->id}}">{{$egg->name}}</option>
                                                 @endforeach
                                             </optgroup>
                                         @endforeach

+ 5 - 3
resources/views/admin/products/edit.blade.php

@@ -224,9 +224,11 @@
                     <div class="col-lg-6">
                         <div class="card">
                             <div class="card-header">
-                                <h5 class="card-title">Product Linking <i data-toggle="popover" data-trigger="hover"
-                                                                          data-content="Linked products will only be available when the user has selected the linked node and/or egg"
-                                                                          class="fas fa-info-circle"></i></h5>
+                                <h5 class="card-title">Product Linking
+                                    <i data-toggle="popover"
+                                       data-trigger="hover"
+                                       data-content="Link your products to nodes and eggs to create dynamic pricing for each option"
+                                       class="fas fa-info-circle"></i></h5>
                             </div>
                             <div class="card-body">
 

+ 42 - 35
resources/views/layouts/main.blade.php

@@ -212,47 +212,34 @@
                             </a>
                         </li>
 
-                        <li class="nav-header">Pterodactyl</li>
+{{--                        <li class="nav-header">Pterodactyl</li>--}}
+
+{{--                        <li class="nav-item">--}}
+{{--                            <a href="{{route('admin.nodes.index')}}"--}}
+{{--                               class="nav-link @if(Request::routeIs('admin.nodes.*')) active @endif">--}}
+{{--                                <i class="nav-icon fas fa-sitemap"></i>--}}
+{{--                                <p>Nodes</p>--}}
+{{--                            </a>--}}
+{{--                        </li>--}}
+
+{{--                        <li class="nav-item">--}}
+{{--                            <a href="{{route('admin.nests.index')}}"--}}
+{{--                               class="nav-link @if(Request::routeIs('admin.nests.*')) active @endif">--}}
+{{--                                <i class="nav-icon fas fa-th-large"></i>--}}
+{{--                                <p>Nests</p>--}}
+{{--                            </a>--}}
+{{--                        </li>--}}
 
-                        <li class="nav-item">
-                            <a href="{{route('admin.nodes.index')}}"
-                               class="nav-link @if(Request::routeIs('admin.nodes.*')) active @endif">
-                                <i class="nav-icon fas fa-sitemap"></i>
-                                <p>Nodes</p>
-                            </a>
-                        </li>
-
-                        <li class="nav-item">
-                            <a href="{{route('admin.nests.index')}}"
-                               class="nav-link @if(Request::routeIs('admin.nests.*')) active @endif">
-                                <i class="nav-icon fas fa-th-large"></i>
-                                <p>Nests</p>
-                            </a>
-                        </li>
-
-                        <li class="nav-header">Logs</li>
-
-                        <li class="nav-item">
-                            <a href="{{route('admin.payments.index')}}"
-                               class="nav-link @if(Request::routeIs('admin.payments.*')) active @endif">
-                                <i class="nav-icon fas fa-money-bill-wave"></i>
-                                <p>Payments
-                                    <span class="badge badge-success right">{{\App\Models\Payment::count()}}</span>
-                                </p>
-                            </a>
-                        </li>
+                        <li class="nav-header">Dashboard</li>
 
                         <li class="nav-item">
-                            <a href="{{route('admin.activitylogs.index')}}"
-                               class="nav-link @if(Request::routeIs('admin.activitylogs.*')) active @endif">
-                                <i class="nav-icon fas fa-clipboard-list"></i>
-                                <p>Activity Logs</p>
+                            <a href="{{route('admin.overview.index')}}"
+                               class="nav-link @if(Request::routeIs('admin.overview.*')) active @endif">
+                                <i class="nav-icon fa fa-gamepad"></i>
+                                <p>Overview</p>
                             </a>
                         </li>
 
-
-                        <li class="nav-header">Dashboard</li>
-
                         <li class="nav-item">
                             <a href="{{route('admin.api.index')}}"
                                class="nav-link @if(Request::routeIs('admin.api.*')) active @endif">
@@ -287,6 +274,26 @@
                             </a>
                         </li>
 
+                        <li class="nav-header">Logs</li>
+
+                        <li class="nav-item">
+                            <a href="{{route('admin.payments.index')}}"
+                               class="nav-link @if(Request::routeIs('admin.payments.*')) active @endif">
+                                <i class="nav-icon fas fa-money-bill-wave"></i>
+                                <p>Payments
+                                    <span class="badge badge-success right">{{\App\Models\Payment::count()}}</span>
+                                </p>
+                            </a>
+                        </li>
+
+                        <li class="nav-item">
+                            <a href="{{route('admin.activitylogs.index')}}"
+                               class="nav-link @if(Request::routeIs('admin.activitylogs.*')) active @endif">
+                                <i class="nav-icon fas fa-clipboard-list"></i>
+                                <p>Activity Logs</p>
+                            </a>
+                        </li>
+
                     @endif
 
                 </ul>

+ 88 - 20
resources/views/servers/create.blade.php

@@ -27,11 +27,12 @@
         <div class="container">
 
             <!-- FORM -->
-            <div class="row">
+            <form action="{{route('servers.store')}}" method="post" class="row">
+                @csrf
                 <div class="col-md-8">
                     <div class="card">
                         <div class="card-header">
-                            <div class="card-title">{{__('Server configuration')}}</div>
+                            <div class="card-title"><i class="fas fa-cogs mr-2"></i>{{__('Server configuration')}}</div>
                         </div>
 
                         @if($productCount === 0 || $nodeCount === 0 || count($nests) === 0 || count($eggs) === 0 )
@@ -62,7 +63,17 @@
                             <i class="fas fa-2x fa-sync-alt"></i>
                         </div>
                         <div class="card-body">
-                            @csrf
+
+                            @if ($errors->any())
+                                <div class="alert alert-danger">
+                                    <ul class="list-group pl-3">
+                                        @foreach ($errors->all() as $error)
+                                            <li>{{ $error }}</li>
+                                        @endforeach
+                                    </ul>
+                                </div>
+                            @endif
+
                             <div class="form-group">
                                 <label for="name">{{__('Name')}}</label>
                                 <input x-model="name" id="name" name="name" type="text" required="required"
@@ -70,7 +81,7 @@
 
                                 @error('name')
                                 <div class="invalid-feedback">
-                                    Please fill out this field.
+                                    {{ $message }}
                                 </div>
                                 @enderror
                             </div>
@@ -80,10 +91,11 @@
                                     <div class="form-group">
                                         <label for="nest">{{__('Software')}}</label>
                                         <select class="custom-select"
+                                                required
                                                 name="nest"
                                                 id="nest"
                                                 x-model="selectedNest"
-                                                @change="setNests(); $refs.egg.selectedIndex = '0'">
+                                                @change="setNests();">
                                             <option selected disabled
                                                     value="null">{{count($nests) > 0 ? __('Please select software..') : __('---')}}</option>
                                             @foreach ($nests as $nest)
@@ -99,11 +111,11 @@
                                         <label for="egg">{{__('Configuration')}}</label>
                                         <div>
                                             <select id="egg"
+                                                    required
                                                     name="egg"
-                                                    x-ref="egg"
                                                     :disabled="eggs.length == 0"
                                                     x-model="selectedEgg"
-                                                    @change="fetchNodes(); $refs.node.selectedIndex = '0'"
+                                                    @change="fetchNodes();"
                                                     required="required"
                                                     class="custom-select">
                                                 <option x-text="getEggInputText()"
@@ -120,8 +132,8 @@
                             <div class="form-group">
                                 <label for="node">{{__('Node')}}</label>
                                 <select name="node"
+                                        required
                                         id="node"
-                                        x-ref="node"
                                         x-model="selectedNode"
                                         :disabled="!fetchedNodes"
                                         @change="fetchProducts();"
@@ -139,10 +151,11 @@
                             <div class="form-group">
                                 <label for="product">{{__('Resources')}}</label>
                                 <select name="product"
+                                        required
                                         id="product"
-                                        x-ref="product"
                                         :disabled="!fetchedProducts"
                                         x-model="selectedProduct"
+                                        @change="updateSelectedObjects()"
                                         class="custom-select">
                                     <option
                                         x-text="getProductInputText()"
@@ -160,48 +173,62 @@
                 </div>
                 <div class="col-md-4 mb-4">
                     <div class="card">
+                        <div class="card-header">
+                            <div class="card-title">
+                                <i class="fas fa-list mr-2"></i>{{__('Server details')}}
+                            </div>
+                        </div>
                         <div class="card-body">
-                            <h4 class="d-flex justify-content-between align-items-center mb-3">
-                                <span class="text-muted">{{__('Server details')}}</span>
-                            </h4>
+
                             <ul class="list-group mb-3">
-                                <li class="list-group-item d-flex justify-content-between lh-condensed">
+                                <li x-show="selectedNestObject.name"
+                                    class="list-group-item d-flex justify-content-between lh-condensed">
                                     <div>
                                         <h6 class="my-0">{{__('Software')}}</h6>
-                                        <small class="text-muted">Brief description</small>
+                                        <small x-text="selectedNestObject?.name ?? '{{__('No selection')}}'"
+                                               class="text-muted"></small>
                                     </div>
                                 </li>
                                 <li class="list-group-item d-flex justify-content-between lh-condensed">
                                     <div>
                                         <h6 class="my-0">{{__('Configuration')}}</h6>
-                                        <small class="text-muted">Brief description</small>
+                                        <small x-text="selectedEggObject?.name ?? '{{__('No selection')}}'"
+                                               class="text-muted"></small>
                                     </div>
                                 </li>
                                 <li
                                     class="list-group-item d-flex justify-content-between lh-condensed">
                                     <div>
                                         <h6 class="my-0">{{__('Node')}}</h6>
-                                        <small class="text-muted">Brief description</small>
+                                        <small x-text="selectedNodeObject?.name ?? '{{__('No selection')}}'"
+                                               class="text-muted"></small>
                                     </div>
                                 </li>
                                 <li
                                     class="list-group-item d-flex justify-content-between lh-condensed">
                                     <div>
                                         <h6 class="my-0">{{__('Resources')}}</h6>
-                                        <small class="text-muted">Brief description</small>
+                                        <small x-text="selectedProductObject?.name ?? '{{__('No selection')}}'"
+                                               class="text-muted"></small>
                                     </div>
                                 </li>
                             </ul>
-                            <ul x-show="selectedProduct" class="list-group">
+                            <ul class="list-group mb-3">
                                 <li class="list-group-item d-flex justify-content-between">
                                     <span>{{CREDITS_DISPLAY_NAME}} {{__('per month')}}</span>
-                                    <strong x-text="selectedProduct"></strong>
+                                    <strong>
+                                        <i x-show="selectedProductObject?.price" class="fas fa-coins"></i>
+                                        <span x-text="selectedProductObject?.price ?? ''"></span>
+                                    </strong>
                                 </li>
                             </ul>
+                            <button :disabled="!isFormValid()" :class="isFormValid() ? '' : 'disabled'" class="btn btn-primary btn-block">
+                                {{__('Create server')}}
+                            </button>
                         </div>
                     </div>
                 </div>
-            </div>
+            </form>
             <!-- END FORM -->
 
         </div>
@@ -212,16 +239,25 @@
     <script>
         function serverApp() {
             return {
+                //loading
                 loading: false,
                 fetchedNodes: false,
                 fetchedProducts: false,
 
+                //input fields
                 name: null,
                 selectedNest: null,
                 selectedEgg: null,
                 selectedNode: null,
                 selectedProduct: null,
 
+                //selected objects based on input
+                selectedNestObject: {},
+                selectedEggObject: {},
+                selectedNodeObject: {},
+                selectedProductObject: {},
+
+                //values
                 nests: {!! $nests !!},
                 eggsSave:{!! $eggs !!}, //store back-end eggs
                 eggs: [],
@@ -239,8 +275,12 @@
                     this.fetchedProducts = false;
                     this.nodes = [];
                     this.products = [];
+                    this.selectedEgg = 'null';
+                    this.selectedNode = 'null';
+                    this.selectedProduct = 'null';
 
                     this.eggs = this.eggsSave.filter(egg => egg.nest_id == this.selectedNest)
+                    this.updateSelectedObjects()
                 },
 
                 /**
@@ -254,6 +294,8 @@
                     this.fetchedProducts = false;
                     this.nodes = [];
                     this.products = [];
+                    this.selectedNode = 'null';
+                    this.selectedProduct = 'null';
 
                     let response = await axios.get(`{{route('products.nodes.egg')}}/${this.selectedEgg}`)
                         .catch(console.error)
@@ -261,6 +303,7 @@
                     this.fetchedNodes = true;
                     this.nodes = response.data
                     this.loading = false;
+                    this.updateSelectedObjects()
                 },
 
                 /**
@@ -272,6 +315,7 @@
                     this.loading = true;
                     this.fetchedProducts = false;
                     this.products = [];
+                    this.selectedProduct = 'null';
 
                     let response = await axios.get(`{{route('products.products.node')}}/${this.selectedNode}`)
                         .catch(console.error)
@@ -279,6 +323,30 @@
                     this.fetchedProducts = true;
                     this.products = response.data
                     this.loading = false;
+                    this.updateSelectedObjects()
+                },
+
+                /**
+                 * @description map selected id's to selected objects
+                 * @note being used in the server info box
+                 */
+                updateSelectedObjects() {
+                    this.selectedNestObject = this.nests.find(nest => nest.id == this.selectedNest) ?? {}
+                    this.selectedEggObject = this.eggs.find(egg => egg.id == this.selectedEgg) ?? {}
+                    this.selectedNodeObject = this.nodes.find(node => node.id == this.selectedNode) ?? {}
+                    this.selectedProductObject = this.products.find(product => product.id == this.selectedProduct) ?? {}
+                },
+
+                /**
+                 * @description check if all options are selected
+                 * @return {boolean}
+                 */
+                isFormValid() {
+                    if (Object.keys(this.selectedNestObject).length === 0) return false;
+                    if (Object.keys(this.selectedEggObject).length === 0) return false;
+                    if (Object.keys(this.selectedNodeObject).length === 0) return false;
+                    if (Object.keys(this.selectedProductObject).length === 0) return false;
+                    return !!this.name;
                 },
 
                 getNodeInputText() {

+ 10 - 8
routes/web.php

@@ -5,6 +5,7 @@ use App\Http\Controllers\Admin\ApplicationApiController;
 use App\Http\Controllers\Admin\ConfigurationController;
 use App\Http\Controllers\Admin\NestsController;
 use App\Http\Controllers\Admin\NodeController;
+use App\Http\Controllers\Admin\OverViewController;
 use App\Http\Controllers\Admin\PaymentController;
 use App\Http\Controllers\Admin\PaypalProductController;
 use App\Http\Controllers\Admin\ProductController;
@@ -60,7 +61,6 @@ Route::middleware(['auth', 'checkSuspended'])->group(function () {
     Route::get('/products/nodes/egg/{egg?}' , [FrontProductController::class , 'getNodesBasedOnEgg'])->name('products.nodes.egg');
     Route::get('/products/products/node/{node?}' , [FrontProductController::class , 'getProductsBasedOnNode'])->name('products.products.node');
 
-
     #payments
     Route::get('checkout/{paypalProduct}', [PaymentController::class, 'checkOut'])->name('checkout');
     Route::get('payment/success', [PaymentController::class, 'success'])->name('payment.success');
@@ -79,6 +79,8 @@ Route::middleware(['auth', 'checkSuspended'])->group(function () {
     #admin
     Route::prefix('admin')->name('admin.')->middleware('admin')->group(function () {
 
+        Route::get('overview', [OverViewController::class , 'index'])->name('overview.index');
+
         Route::resource('activitylogs', ActivityLogController::class);
 
         Route::get("users.json", [UserController::class, "json"])->name('users.json');
@@ -107,13 +109,13 @@ Route::middleware(['auth', 'checkSuspended'])->group(function () {
         Route::get('payments/datatable', [PaymentController::class, 'datatable'])->name('payments.datatable');
         Route::get('payments', [PaymentController::class, 'index'])->name('payments.index');
 
-        Route::get('nodes/datatable', [NodeController::class, 'datatable'])->name('nodes.datatable');
-        Route::get('nodes/sync', [NodeController::class, 'sync'])->name('nodes.sync');
-        Route::resource('nodes', NodeController::class);
-
-        Route::get('nests/datatable', [NestsController::class, 'datatable'])->name('nests.datatable');
-        Route::get('nests/sync', [NestsController::class, 'sync'])->name('nests.sync');
-        Route::resource('nests', NestsController::class);
+//        Route::get('nodes/datatable', [NodeController::class, 'datatable'])->name('nodes.datatable');
+//        Route::get('nodes/sync', [NodeController::class, 'sync'])->name('nodes.sync');
+//        Route::resource('nodes', NodeController::class);
+//
+//        Route::get('nests/datatable', [NestsController::class, 'datatable'])->name('nests.datatable');
+//        Route::get('nests/sync', [NestsController::class, 'sync'])->name('nests.sync');
+//        Route::resource('nests', NestsController::class);
 
         Route::get('configurations/datatable', [ConfigurationController::class, 'datatable'])->name('configurations.datatable');
         Route::patch('configurations/updatevalue', [ConfigurationController::class, 'updatevalue'])->name('configurations.updatevalue');

+ 2 - 0
storage/debugbar/.gitignore

@@ -0,0 +1,2 @@
+*
+!.gitignore