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

This commit is contained in:
AVMG20 2021-11-07 01:47:16 +01:00
parent 16a7d174e9
commit 24e6d48496
15 changed files with 681 additions and 174 deletions

View file

@ -11,10 +11,22 @@ use Exception;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Http\Client\Response;
use Illuminate\Support\Facades\Http;
use Illuminate\Validation\Validator;
class Pterodactyl
{
/**
* @return null
* @throws Exception
*/
public static function getNests()
{
$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)
/**
* @return PendingRequest
*/
@ -27,18 +39,57 @@ class Pterodactyl
])->baseUrl(env('PTERODACTYL_URL') . '/api');
}
//TODO: Extend error handling (maybe logger for more errors when debugging)
/**
* Get user by pterodactyl id
* @param int $pterodactylId
* @return Exception
*/
private static function getException(): Exception
{
return new Exception('Request Failed, is pterodactyl set-up correctly?');
}
/**
* @param Nest $nest
* @return mixed
* @throws Exception
*/
public static function getEggs(Nest $nest)
{
$response = self::client()->get("/application/nests/{$nest->id}/eggs?include=nest,variables");
if ($response->failed()) throw self::getException();
return $response->json()['data'];
}
/**
* @return mixed
* @throws Exception
*/
public static function getNodes()
{
$response = self::client()->get('/application/nodes');
if ($response->failed()) throw self::getException();
dd($response->json());
return $response->json()['data'];
}
/**
* @return mixed
* @throws Exception
*/
public static function getLocations()
{
$response = self::client()->get('/application/locations');
if ($response->failed()) throw self::getException();
return $response->json()['data'];
}
/**
* @param Node $node
* @return mixed
*/
public function getUser(int $pterodactylId)
public static function getFreeAllocationId(Node $node)
{
$response = self::client()->get("/application/users/{$pterodactylId}");
if ($response->failed()) return $response->json();
return $response->json()['attributes'];
return self::getFreeAllocations($node)[0]['attributes']['id'] ?? null;
}
/**
@ -62,64 +113,6 @@ class Pterodactyl
return $freeAllocations;
}
/**
* @return null
* @throws Exception
*/
public static function getNests()
{
$response = self::client()->get('/application/nests');
if ($response->failed()) throw self::getException();
return $response->json()['data'];
}
/**
* @param Nest $nest
* @return mixed
* @throws Exception
*/
public static function getEggs(Nest $nest)
{
$response = self::client()->get("/application/nests/{$nest->id}/eggs?include=nest,variables");
if ($response->failed()) throw self::getException();
return $response->json()['data'];
}
/**
* @return mixed
* @throws Exception
*/
public static function getNodes()
{
$response = self::client()->get('/application/nodes');
if ($response->failed()) throw self::getException();
return $response->json()['data'];
}
/**
* @return mixed
* @throws Exception
*/
public static function getLocations()
{
$response = self::client()->get('/application/locations');
if ($response->failed()) throw self::getException();
return $response->json()['data'];
}
/**
* @param Node $node
* @return mixed
*/
public static function getFreeAllocationId(Node $node)
{
return self::getFreeAllocations($node)[0]['attributes']['id'] ?? null;
}
/**
* @param Node $node
* @throws Exception
@ -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'];
}
}

View file

@ -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
{
/**

View file

@ -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
{
/**

View file

@ -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, '.', ''),
]);
}
}

View file

@ -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());
}
}

View file

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

View file

@ -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",

222
composer.lock generated
View file

@ -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"
}

View file

@ -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

View file

@ -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

View file

@ -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">

View file

@ -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-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>
{{-- <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">Dashboard</li>
<li class="nav-item">
<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-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>

View file

@ -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() {

View file

@ -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
storage/debugbar/.gitignore vendored Normal file
View file

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