Linking of products to nodes and/or eggs

This commit is contained in:
AVMG20 2021-07-06 21:38:13 +02:00
parent 77444496a2
commit bd45b3b713
12 changed files with 385 additions and 57 deletions

View file

@ -3,6 +3,10 @@
namespace App\Http\Controllers\Admin; namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Egg;
use App\Models\Location;
use App\Models\Nest;
use App\Models\Node;
use App\Models\Product; use App\Models\Product;
use Exception; use Exception;
use Illuminate\Contracts\Foundation\Application; use Illuminate\Contracts\Foundation\Application;
@ -32,7 +36,10 @@ class ProductController extends Controller
*/ */
public function create() public function create()
{ {
return view('admin.products.create'); return view('admin.products.create' , [
'locations' => Location::with('nodes')->get(),
'nests' => Nest::with('eggs')->get(),
]);
} }
/** /**
@ -55,11 +62,17 @@ class ProductController extends Controller
"databases" => "required|numeric|max:1000000|min:0", "databases" => "required|numeric|max:1000000|min:0",
"backups" => "required|numeric|max:1000000|min:0", "backups" => "required|numeric|max:1000000|min:0",
"allocations" => "required|numeric|max:1000000|min:0", "allocations" => "required|numeric|max:1000000|min:0",
"nodes.*" => "required|exists:nodes,id",
"eggs.*" => "required|exists:eggs,id",
"disabled" => "nullable", "disabled" => "nullable",
]); ]);
$disabled = !is_null($request->input('disabled')); $disabled = !is_null($request->input('disabled'));
Product::create(array_merge($request->all(), ['disabled' => $disabled])); $product = Product::create(array_merge($request->all(), ['disabled' => $disabled]));
#link nodes and eggs
$product->eggs()->attach($request->input('eggs'));
$product->nodes()->attach($request->input('nodes'));
return redirect()->route('admin.products.index')->with('success', 'product has been created!'); return redirect()->route('admin.products.index')->with('success', 'product has been created!');
} }
@ -86,7 +99,9 @@ class ProductController extends Controller
public function edit(Product $product) public function edit(Product $product)
{ {
return view('admin.products.edit', [ return view('admin.products.edit', [
'product' => $product 'product' => $product,
'locations' => Location::with('nodes')->get(),
'nests' => Nest::with('eggs')->get(),
]); ]);
} }
@ -111,12 +126,20 @@ class ProductController extends Controller
"databases" => "required|numeric|max:1000000|min:0", "databases" => "required|numeric|max:1000000|min:0",
"backups" => "required|numeric|max:1000000|min:0", "backups" => "required|numeric|max:1000000|min:0",
"allocations" => "required|numeric|max:1000000|min:0", "allocations" => "required|numeric|max:1000000|min:0",
"nodes.*" => "required|exists:nodes,id",
"eggs.*" => "required|exists:eggs,id",
"disabled" => "nullable", "disabled" => "nullable",
]); ]);
$disabled = !is_null($request->input('disabled')); $disabled = !is_null($request->input('disabled'));
$product->update(array_merge($request->all(), ['disabled' => $disabled])); $product->update(array_merge($request->all(), ['disabled' => $disabled]));
#link nodes and eggs
$product->eggs()->detach();
$product->nodes()->detach();
$product->eggs()->attach($request->input('eggs'));
$product->nodes()->attach($request->input('nodes'));
return redirect()->route('admin.products.index')->with('success', 'product has been updated!'); return redirect()->route('admin.products.index')->with('success', 'product has been updated!');
} }
@ -174,6 +197,12 @@ class ProductController extends Controller
->addColumn('servers', function (Product $product) { ->addColumn('servers', function (Product $product) {
return $product->servers()->count(); return $product->servers()->count();
}) })
->addColumn('nodes', function (Product $product) {
return $product->nodes()->count();
})
->addColumn('eggs', function (Product $product) {
return $product->eggs()->count();
})
->addColumn('disabled', function (Product $product) { ->addColumn('disabled', function (Product $product) {
$checked = $product->disabled == false ? "checked" : ""; $checked = $product->disabled == false ? "checked" : "";
return ' return '

View file

@ -2,6 +2,8 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Models\Egg;
use App\Models\Product;
use App\Models\UsefulLink; use App\Models\UsefulLink;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
@ -16,6 +18,9 @@ class HomeController extends Controller
/** Show the application dashboard. */ /** Show the application dashboard. */
public function index(Request $request) public function index(Request $request)
{ {
dd(Product::first()->nodes()->get() , Product::first()->eggs()->get());
$usage = 0; $usage = 0;
foreach (Auth::user()->servers as $server){ foreach (Auth::user()->servers as $server){

View file

@ -6,6 +6,7 @@ use App\Classes\Pterodactyl;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Egg extends Model class Egg extends Model
{ {
@ -23,14 +24,6 @@ class Egg extends Model
'environment', 'environment',
]; ];
/**
* @return BelongsTo
*/
public function nest()
{
return $this->belongsTo(Nest::class, 'id', 'nest_id');
}
/** /**
* @return array * @return array
*/ */
@ -39,7 +32,7 @@ class Egg extends Model
$array = []; $array = [];
foreach (json_decode($this->environment) as $variable) { foreach (json_decode($this->environment) as $variable) {
foreach ($variable as $key => $value){ foreach ($variable as $key => $value) {
$array[$key] = $value; $array[$key] = $value;
} }
} }
@ -47,12 +40,13 @@ class Egg extends Model
return $array; return $array;
} }
public static function syncEggs(){ public static function syncEggs()
{
Nest::all()->each(function (Nest $nest) { Nest::all()->each(function (Nest $nest) {
$eggs = Pterodactyl::getEggs($nest); $eggs = Pterodactyl::getEggs($nest);
foreach ($eggs as $egg){ foreach ($eggs as $egg) {
$array = []; $array = [];
$environment = []; $environment = [];
@ -64,17 +58,32 @@ class Egg extends Model
$array['startup'] = $egg['attributes']['startup']; $array['startup'] = $egg['attributes']['startup'];
//get environment variables //get environment variables
foreach ($egg['attributes']['relationships']['variables']['data'] as $variable){ foreach ($egg['attributes']['relationships']['variables']['data'] as $variable) {
$environment[$variable['attributes']['env_variable']] = $variable['attributes']['default_value']; $environment[$variable['attributes']['env_variable']] = $variable['attributes']['default_value'];
} }
$array['environment'] = json_encode([$environment]); $array['environment'] = json_encode([$environment]);
self::firstOrCreate(['id' => $array['id']] , $array); self::firstOrCreate(['id' => $array['id']], $array);
} }
}); });
} }
/**
* @return BelongsTo
*/
public function nest()
{
return $this->belongsTo(Nest::class, 'id', 'nest_id');
}
/**
* @return BelongsToMany
*/
public function products()
{
return $this->belongsToMany(Product::class);
}
} }

View file

@ -7,6 +7,7 @@ use Exception;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Node extends Model class Node extends Model
{ {
@ -48,4 +49,12 @@ class Node extends Model
} }
} }
/**
* @return BelongsToMany
*/
public function products()
{
return $this->belongsToMany(Product::class);
}
} }

View file

@ -7,6 +7,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Spatie\Activitylog\Traits\LogsActivity; use Spatie\Activitylog\Traits\LogsActivity;
class Product extends Model class Product extends Model
@ -25,6 +26,11 @@ class Product extends Model
$product->{$product->getKeyName()} = $client->generateId($size = 21); $product->{$product->getKeyName()} = $client->generateId($size = 21);
}); });
static::deleting(function(Product $product) {
$product->nodes()->detach();
$product->eggs()->detach();
});
} }
public function getHourlyPrice() public function getHourlyPrice()
@ -45,8 +51,22 @@ class Product extends Model
/** /**
* @return BelongsTo * @return BelongsTo
*/ */
public function servers(): BelongsTo public function servers()
{ {
return $this->belongsTo(Server::class , 'id' , 'product_id'); return $this->belongsTo(Server::class , 'id' , 'product_id');
} }
/**
* @return BelongsToMany
*/
public function eggs() {
return $this->belongsToMany(Egg::class);
}
/**
* @return BelongsToMany
*/
public function nodes() {
return $this->belongsToMany(Node::class);
}
} }

View file

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateEggProductTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('egg_product', function (Blueprint $table) {
$table->foreignId('egg_id')->constrained();
$table->foreignUuid('product_id')->constrained();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('egg_product');
}
}

View file

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateNodeProductTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('node_product', function (Blueprint $table) {
$table->foreignId('node_id')->constrained();
$table->foreignUuid('product_id')->constrained();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('node_product');
}
}

View file

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddDisabledToEggsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('eggs', function (Blueprint $table) {
$table->boolean('disabled')->default(false);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('eggs', function (Blueprint $table) {
$table->dropColumn('disabled');
});
}
}

View file

@ -24,17 +24,24 @@
<!-- MAIN CONTENT --> <!-- MAIN CONTENT -->
<section class="content"> <section class="content">
<div class="container-fluid"> <div class="container-fluid">
<form action="{{route('admin.products.store')}}" method="POST">
@csrf
<div class="row">
<div class="col-lg-6">
<div class="card">
<div class="card-header">
<h5 class="card-title">Product Details</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-lg-6">
<div class="card">
<div class="card-body">
<form action="{{route('admin.products.store')}}" method="POST">
@csrf
<div class="d-flex flex-row-reverse"> <div class="d-flex flex-row-reverse">
<div class="custom-control custom-switch"> <div class="custom-control custom-switch">
<input type="checkbox" name="disabled" class="custom-control-input custom-control-input-danger" id="switch1"> <input type="checkbox" name="disabled"
<label class="custom-control-label" for="switch1">Disabled <i data-toggle="popover" data-trigger="hover" data-content="Will hide this option from being selected" class="fas fa-info-circle"></i></label> class="custom-control-input custom-control-input-danger" id="switch1">
<label class="custom-control-label" for="switch1">Disabled <i
data-toggle="popover" data-trigger="hover"
data-content="Will hide this option from being selected"
class="fas fa-info-circle"></i></label>
</div> </div>
</div> </div>
@ -105,7 +112,10 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="description">Description <i data-toggle="popover" data-trigger="hover" data-content="This is what the users sees" class="fas fa-info-circle"></i></label> <label for="description">Description <i data-toggle="popover"
data-trigger="hover"
data-content="This is what the users sees"
class="fas fa-info-circle"></i></label>
<textarea id="description" name="description" <textarea id="description" name="description"
type="text" type="text"
class="form-control @error('description') is-invalid @enderror" class="form-control @error('description') is-invalid @enderror"
@ -190,16 +200,80 @@
Submit Submit
</button> </button>
</div> </div>
</form>
</div>
</div> </div>
</div> </div>
<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>
</div>
<div class="card-body">
<div class="form-group">
<label for="nodes">Nodes</label>
<select id="nodes" 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 value="{{$node->id}}">{{$node->name}}</option>
@endforeach
</optgroup>
@endforeach
</select>
@error('nodes')
<div class="text-danger">
{{$message}}
</div>
@enderror
<div class="text-muted">
This product will only be available for these nodes
</div>
</div>
<div class="form-group">
<label for="eggs">Eggs</label>
<select id="eggs" 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 value="{{$egg->id}}">{{$egg->name}}</option>
@endforeach
</optgroup>
@endforeach
</select>
@error('eggs')
<div class="text-danger">
{{$message}}
</div>
@enderror
<div class="text-muted">
This product will only be available for these eggs
</div>
</div>
</div>
</div>
</div>
</div> </div>
</div> </form>
</div> </div>
</section> </section>
<!-- END CONTENT --> <!-- END CONTENT -->
<script>
document.addEventListener('DOMContentLoaded', (event) => {
$('.custom-select').select2();
})
</script>
@endsection @endsection

View file

@ -12,7 +12,8 @@
<ol class="breadcrumb float-sm-right"> <ol class="breadcrumb float-sm-right">
<li class="breadcrumb-item"><a href="{{route('home')}}">Dashboard</a></li> <li class="breadcrumb-item"><a href="{{route('home')}}">Dashboard</a></li>
<li class="breadcrumb-item"><a href="{{route('admin.products.index')}}">Products</a></li> <li class="breadcrumb-item"><a href="{{route('admin.products.index')}}">Products</a></li>
<li class="breadcrumb-item"><a class="text-muted" href="{{route('admin.products.edit' , $product->id)}}">Edit</a> <li class="breadcrumb-item"><a class="text-muted"
href="{{route('admin.products.edit' , $product->id)}}">Edit</a>
</li> </li>
</ol> </ol>
</div> </div>
@ -24,32 +25,42 @@
<!-- MAIN CONTENT --> <!-- MAIN CONTENT -->
<section class="content"> <section class="content">
<div class="container-fluid"> <div class="container-fluid">
<form action="{{route('admin.products.update' , $product->id)}}" method="POST">
@csrf
@method('PATCH')
<div class="row">
<div class="col-lg-6">
<div class="row"> @if($product->servers()->count() > 0)
<div class="col-lg-6"> <div class="callout callout-danger">
<h4>Editing the resource options will not automatically update the servers on
pterodactyl's side!</h4>
<p class="text-muted">Automatically updating resource options on pterodactyl side is on
my todo list :)</p>
</div>
@endif
@if($product->servers()->count() > 0) <div class="card">
<div class="callout callout-danger"> <div class="card-header">
<h4>Editing the resource options will not automatically update the servers on pterodactyl's side!</h4> <h5 class="card-title">Product Details</h5>
<p class="text-muted">Automatically updating resource options on pterodactyl side is on my todo list :)</p> </div>
</div> <div class="card-body">
@endif
<div class="card">
<div class="card-body">
<form action="{{route('admin.products.update' , $product->id)}}" method="POST">
@csrf
@method('PATCH')
<div class="d-flex flex-row-reverse"> <div class="d-flex flex-row-reverse">
<div class="custom-control custom-switch"> <div class="custom-control custom-switch">
<input type="checkbox" @if($product->disabled) checked @endif name="disabled" class="custom-control-input custom-control-input-danger" id="switch1"> <input type="checkbox" @if($product->disabled) checked @endif name="disabled"
<label class="custom-control-label" for="switch1">Disabled <i data-toggle="popover" data-trigger="hover" data-content="Will hide this option from being selected" class="fas fa-info-circle"></i></label> class="custom-control-input custom-control-input-danger" id="switch1">
<label class="custom-control-label" for="switch1">Disabled <i
data-toggle="popover" data-trigger="hover"
data-content="Will hide this option from being selected"
class="fas fa-info-circle"></i></label>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-lg-6"> <div class="col-lg-6">
<div class="form-group"> <div class="form-group">
<label for="name">Name</label> <label for="name">Name</label>
<input value="{{$product->name}}" id="name" name="name" type="text" <input value="{{$product->name}}" id="name" name="name" type="text"
@ -115,7 +126,10 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="description">Description <i data-toggle="popover" data-trigger="hover" data-content="This is what the users sees" class="fas fa-info-circle"></i></label> <label for="description">Description <i data-toggle="popover"
data-trigger="hover"
data-content="This is what the users sees"
class="fas fa-info-circle"></i></label>
<textarea id="description" name="description" <textarea id="description" name="description"
type="text" type="text"
class="form-control @error('description') is-invalid @enderror" class="form-control @error('description') is-invalid @enderror"
@ -200,16 +214,82 @@
Submit Submit
</button> </button>
</div> </div>
</form>
</div>
</div> </div>
</div> </div>
</div>
</div>
<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>
</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" name="nodes[]"
multiple="multiple" autocomplete="off">
@foreach($locations as $location)
<optgroup label="{{$location->name}}">
@foreach($location->nodes as $node)
<option @if($product->nodes->contains('id' , $node->id)) selected
@endif value="{{$node->id}}">{{$node->name}}</option>
@endforeach
</optgroup>
@endforeach
</select>
@error('nodes')
<div class="text-danger">
{{$message}}
</div>
@enderror
<div class="text-muted">
This product will only be available for these nodes
</div>
</div>
<div class="form-group">
<label for="eggs">Eggs</label>
<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($product->eggs->contains('id' , $egg->id)) selected
@endif value="{{$egg->id}}">{{$egg->name}}</option>
@endforeach
</optgroup>
@endforeach
</select>
@error('eggs')
<div class="text-danger">
{{$message}}
</div>
@enderror
<div class="text-muted">
This product will only be available for these eggs
</div>
</div>
</div>
</div>
</div>
</div>
</form>
</div> </div>
</section> </section>
<!-- END CONTENT --> <!-- END CONTENT -->
<script>
document.addEventListener('DOMContentLoaded', (event) => {
$('.custom-select').select2();
})
</script>
@endsection @endsection

View file

@ -46,10 +46,10 @@
<th>Cpu</th> <th>Cpu</th>
<th>Swap</th> <th>Swap</th>
<th>Disk</th> <th>Disk</th>
<th>IO</th>
<th>Databases</th> <th>Databases</th>
<th>Backups</th> <th>Backups</th>
<th>Allocations</th> <th>Eggs</th>
<th>Nodes</th>
<th>Servers</th> <th>Servers</th>
<th>Created at</th> <th>Created at</th>
<th></th> <th></th>
@ -79,6 +79,7 @@
processing: true, processing: true,
serverSide: true, serverSide: true,
stateSave: true, stateSave: true,
order: [[ 2, "asc" ]],
ajax: "{{route('admin.products.datatable')}}", ajax: "{{route('admin.products.datatable')}}",
columns: [ columns: [
{data: 'disabled'}, {data: 'disabled'},
@ -88,10 +89,10 @@
{data: 'cpu'}, {data: 'cpu'},
{data: 'swap'}, {data: 'swap'},
{data: 'disk'}, {data: 'disk'},
{data: 'io'},
{data: 'databases'}, {data: 'databases'},
{data: 'backups'}, {data: 'backups'},
{data: 'allocations'}, {data: 'nodes', sortable: false},
{data: 'eggs', sortable: false},
{data: 'servers', sortable: false}, {data: 'servers', sortable: false},
{data: 'created_at'}, {data: 'created_at'},
{data: 'actions', sortable: false}, {data: 'actions', sortable: false},

View file

@ -16,6 +16,9 @@
{{-- summernote --}} {{-- summernote --}}
<link rel="stylesheet" href="{{asset('plugins/summernote/summernote-bs4.min.css')}}"> <link rel="stylesheet" href="{{asset('plugins/summernote/summernote-bs4.min.css')}}">
{{-- select2 --}}
<link rel="stylesheet" href="{{asset('plugins/select2/css/select2.min.css')}}">
<link rel="stylesheet" href="{{asset('css/app.css')}}"> <link rel="stylesheet" href="{{asset('css/app.css')}}">
<link rel="preload" href="{{asset('plugins/fontawesome-free/css/all.min.css')}}" as="style" <link rel="preload" href="{{asset('plugins/fontawesome-free/css/all.min.css')}}" as="style"
onload="this.onload=null;this.rel='stylesheet'"> onload="this.onload=null;this.rel='stylesheet'">
@ -315,6 +318,8 @@
<script type="text/javascript" src="https://cdn.datatables.net/v/bs4/dt-1.10.24/datatables.min.js"></script> <script type="text/javascript" src="https://cdn.datatables.net/v/bs4/dt-1.10.24/datatables.min.js"></script>
<!-- Summernote --> <!-- Summernote -->
<script src="{{asset('plugins/summernote/summernote-bs4.min.js')}}"></script> <script src="{{asset('plugins/summernote/summernote-bs4.min.js')}}"></script>
<!-- select2 -->
<script src="{{asset('plugins/select2/js/select2.min.js')}}"></script>
<script> <script>
$(document).ready(function () { $(document).ready(function () {