[Feature] Addon implemented
This commit is contained in:
parent
159ba02c84
commit
130a3b308b
|
@ -14,7 +14,7 @@ use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
class ProductController extends Controller
|
class ProductController extends Controller
|
||||||
{
|
{
|
||||||
private $pterodactyl;
|
private $pterodactyl;
|
||||||
|
|
||||||
public function __construct(PterodactylSettings $ptero_settings)
|
public function __construct(PterodactylSettings $ptero_settings)
|
||||||
|
@ -97,30 +97,41 @@ class ProductController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Node $node
|
* @param Int $location
|
||||||
* @param Egg $egg
|
* @param Egg $egg
|
||||||
* @return Collection|JsonResponse
|
* @return Collection|JsonResponse
|
||||||
*/
|
*/
|
||||||
public function getProductsBasedOnNode(Egg $egg, Node $node)
|
public function getProductsBasedOnLocation(Egg $egg, Int $location)
|
||||||
{
|
{
|
||||||
if (is_null($egg->id) || is_null($node->id)) {
|
if (is_null($egg->id) || is_null($location)) {
|
||||||
return response()->json('node and egg id is required', '400');
|
return response()->json('location and egg id is required', '400');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get all nodes in this location
|
||||||
|
$nodes = Node::query()
|
||||||
|
->where('location_id', '=', $location)
|
||||||
|
->get();
|
||||||
|
|
||||||
$products = Product::query()
|
$products = Product::query()
|
||||||
->where('disabled', '=', false)
|
->where('disabled', '=', false)
|
||||||
->whereHas('nodes', function (Builder $builder) use ($node) {
|
->whereHas('nodes', function (Builder $builder) use ($nodes) {
|
||||||
$builder->where('id', '=', $node->id);
|
$builder->whereIn('id', $nodes->map(function ($node) {
|
||||||
|
return $node->id;
|
||||||
|
}));
|
||||||
})
|
})
|
||||||
->whereHas('eggs', function (Builder $builder) use ($egg) {
|
->whereHas('eggs', function (Builder $builder) use ($egg) {
|
||||||
$builder->where('id', '=', $egg->id);
|
$builder->where('id', '=', $egg->id);
|
||||||
})
|
})
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
$pteroNode = $this->pterodactyl->getNode($node->id);
|
// Instead of the old node check, we will check if the product fits in any given node in the location
|
||||||
foreach ($products as $key => $product) {
|
foreach ($products as $key => $product) {
|
||||||
if ($product->memory > ($pteroNode['memory'] * ($pteroNode['memory_overallocate'] + 100) / 100) - $pteroNode['allocated_resources']['memory'] || $product->disk > ($pteroNode['disk'] * ($pteroNode['disk_overallocate'] + 100) / 100) - $pteroNode['allocated_resources']['disk']) {
|
$product->doesNotFit = false;
|
||||||
$product->doesNotFit = true;
|
foreach ($nodes as $node) {
|
||||||
|
$pteroNode = $this->pterodactyl->getNode($node->id);
|
||||||
|
if ($product->memory > ($pteroNode['memory'] * ($pteroNode['memory_overallocate'] + 100) / 100) - $pteroNode['allocated_resources']['memory'] || $product->disk > ($pteroNode['disk'] * ($pteroNode['disk_overallocate'] + 100) / 100) - $pteroNode['allocated_resources']['disk']) {
|
||||||
|
$product->doesNotFit = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -141,13 +141,10 @@ class ServerController extends Controller
|
||||||
$product = Product::findOrFail(FacadesRequest::input('product'));
|
$product = Product::findOrFail(FacadesRequest::input('product'));
|
||||||
|
|
||||||
// Get node resource allocation info
|
// Get node resource allocation info
|
||||||
$node = $product->nodes()->findOrFail(FacadesRequest::input('node'));
|
$location = FacadesRequest::input('location');
|
||||||
$nodeName = $node->name;
|
$availableNode = $this->getAvailableNode($location, $product);
|
||||||
|
if (!$availableNode) {
|
||||||
// Check if node has enough memory and disk space
|
return redirect()->route('servers.index')->with('error', __("The chosen location doesn't have the required memory or disk left to allocate this product."));
|
||||||
$checkResponse = $this->pterodactyl->checkNodeResources($node, $product->memory, $product->disk);
|
|
||||||
if ($checkResponse == false) {
|
|
||||||
return redirect()->route('servers.index')->with('error', __("The node '" . $nodeName . "' doesn't have the required memory or disk left to allocate this product."));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Min. Credits
|
// Min. Credits
|
||||||
|
@ -179,7 +176,7 @@ class ServerController extends Controller
|
||||||
/** Store a newly created resource in storage. */
|
/** Store a newly created resource in storage. */
|
||||||
public function store(Request $request, UserSettings $user_settings, ServerSettings $server_settings, GeneralSettings $generalSettings)
|
public function store(Request $request, UserSettings $user_settings, ServerSettings $server_settings, GeneralSettings $generalSettings)
|
||||||
{
|
{
|
||||||
/** @var Node $node */
|
/** @var Location $location */
|
||||||
/** @var Egg $egg */
|
/** @var Egg $egg */
|
||||||
/** @var Product $product */
|
/** @var Product $product */
|
||||||
$validate_configuration = $this->validateConfigurationRules($user_settings, $server_settings, $generalSettings);
|
$validate_configuration = $this->validateConfigurationRules($user_settings, $server_settings, $generalSettings);
|
||||||
|
@ -190,15 +187,23 @@ class ServerController extends Controller
|
||||||
|
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'name' => 'required|max:191',
|
'name' => 'required|max:191',
|
||||||
'node' => 'required|exists:nodes,id',
|
'location' => 'required|exists:locations,id',
|
||||||
'egg' => 'required|exists:eggs,id',
|
'egg' => 'required|exists:eggs,id',
|
||||||
'product' => 'required|exists:products,id',
|
'product' => 'required|exists:products,id',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
//get required resources
|
// Get the product and egg
|
||||||
$product = Product::query()->findOrFail($request->input('product'));
|
$product = Product::query()->findOrFail($request->input('product'));
|
||||||
$egg = $product->eggs()->findOrFail($request->input('egg'));
|
$egg = $product->eggs()->findOrFail($request->input('egg'));
|
||||||
$node = $product->nodes()->findOrFail($request->input('node'));
|
|
||||||
|
// Get an available node
|
||||||
|
$location = $request->input('location');
|
||||||
|
$availableNode = $this->getAvailableNode($location, $product);
|
||||||
|
$node = Node::query()->find($availableNode);
|
||||||
|
|
||||||
|
if(!$node) {
|
||||||
|
return redirect()->route('servers.index')->with('error', __("No nodes satisfying the requirements for automatic deployment on this location were found."));
|
||||||
|
}
|
||||||
|
|
||||||
$server = $request->user()->servers()->create([
|
$server = $request->user()->servers()->create([
|
||||||
'name' => $request->input('name'),
|
'name' => $request->input('name'),
|
||||||
|
@ -316,7 +321,7 @@ class ServerController extends Controller
|
||||||
})
|
})
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
// Set the each product eggs array to just contain the eggs name
|
// Set each product eggs array to just contain the eggs name
|
||||||
foreach ($products as $product) {
|
foreach ($products as $product) {
|
||||||
$product->eggs = $product->eggs->pluck('name')->toArray();
|
$product->eggs = $product->eggs->pluck('name')->toArray();
|
||||||
if ($product->memory - $currentProduct->memory > ($pteroNode['memory'] * ($pteroNode['memory_overallocate'] + 100) / 100) - $pteroNode['allocated_resources']['memory'] || $product->disk - $currentProduct->disk > ($pteroNode['disk'] * ($pteroNode['disk_overallocate'] + 100) / 100) - $pteroNode['allocated_resources']['disk']) {
|
if ($product->memory - $currentProduct->memory > ($pteroNode['memory'] * ($pteroNode['memory_overallocate'] + 100) / 100) - $pteroNode['allocated_resources']['memory'] || $product->disk - $currentProduct->disk > ($pteroNode['disk'] * ($pteroNode['disk_overallocate'] + 100) / 100) - $pteroNode['allocated_resources']['disk']) {
|
||||||
|
@ -356,8 +361,8 @@ class ServerController extends Controller
|
||||||
// Check if node has enough memory and disk space
|
// Check if node has enough memory and disk space
|
||||||
$requireMemory = $newProduct->memory - $oldProduct->memory;
|
$requireMemory = $newProduct->memory - $oldProduct->memory;
|
||||||
$requiredisk = $newProduct->disk - $oldProduct->disk;
|
$requiredisk = $newProduct->disk - $oldProduct->disk;
|
||||||
$checkResponse = $this->pterodactyl->checkNodeResources($node, $requireMemory, $requiredisk);
|
$nodeFree = $this->pterodactyl->checkNodeResources($node, $requireMemory, $requiredisk);
|
||||||
if ($checkResponse == false) {
|
if (!$nodeFree) {
|
||||||
return redirect()->route('servers.index')->with('error', __("The node '" . $nodeName . "' doesn't have the required memory or disk left to upgrade the server."));
|
return redirect()->route('servers.index')->with('error', __("The node '" . $nodeName . "' doesn't have the required memory or disk left to upgrade the server."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -412,4 +417,30 @@ class ServerController extends Controller
|
||||||
return redirect()->route('servers.show', ['server' => $server->id])->with('error', __('Not Enough Balance for Upgrade'));
|
return redirect()->route('servers.show', ['server' => $server->id])->with('error', __('Not Enough Balance for Upgrade'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $location
|
||||||
|
* @param Product $product
|
||||||
|
* @return int | null Node ID
|
||||||
|
*/
|
||||||
|
private function getAvailableNode(string $location, Product $product)
|
||||||
|
{
|
||||||
|
$collection = Node::query()->where('location_id', $location)->get();
|
||||||
|
|
||||||
|
// loop through nodes and check if the node has enough resources
|
||||||
|
foreach ($collection as $node) {
|
||||||
|
// Check if the node has enough memory and disk space
|
||||||
|
$freeNode = $this->pterodactyl->checkNodeResources($node, $product->memory, $product->disk);
|
||||||
|
// Remove the node from the collection if it doesn't have enough resources
|
||||||
|
if (!$freeNode) {
|
||||||
|
$collection->forget($node['id']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($collection->isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $collection->first()['id'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,7 +71,7 @@
|
||||||
"Change Status": "Change Status",
|
"Change Status": "Change Status",
|
||||||
"Profile updated": "Profile updated",
|
"Profile updated": "Profile updated",
|
||||||
"Server limit reached!": "Server limit reached!",
|
"Server limit reached!": "Server limit reached!",
|
||||||
"The node '\" . $nodeName . \"' doesn't have the required memory or disk left to allocate this product.": "The node '\" . $nodeName . \"' doesn't have the required memory or disk left to allocate this product.",
|
"The chosen location doesn't have the required memory or disk left to allocate this product.": "The chosen location doesn't have the required memory or disk left to allocate this product.",
|
||||||
"You are required to verify your email address before you can create a server.": "You are required to verify your email address before you can create a server.",
|
"You are required to verify your email address before you can create a server.": "You are required to verify your email address before you can create a server.",
|
||||||
"You are required to link your discord account before you can create a server.": "You are required to link your discord account before you can create a server.",
|
"You are required to link your discord account before you can create a server.": "You are required to link your discord account before you can create a server.",
|
||||||
"Server created": "Server created",
|
"Server created": "Server created",
|
||||||
|
|
|
@ -95,7 +95,7 @@ Route::middleware(['auth', 'checkSuspended'])->group(function () {
|
||||||
//routes made for server create page to fetch product info
|
//routes made for server create page to fetch product info
|
||||||
Route::get('/products/nodes/egg/{egg?}', [FrontProductController::class, 'getNodesBasedOnEgg'])->name('products.nodes.egg');
|
Route::get('/products/nodes/egg/{egg?}', [FrontProductController::class, 'getNodesBasedOnEgg'])->name('products.nodes.egg');
|
||||||
Route::get('/products/locations/egg/{egg?}', [FrontProductController::class, 'getLocationsBasedOnEgg'])->name('products.locations.egg');
|
Route::get('/products/locations/egg/{egg?}', [FrontProductController::class, 'getLocationsBasedOnEgg'])->name('products.locations.egg');
|
||||||
Route::get('/products/products/{egg?}/{node?}', [FrontProductController::class, 'getProductsBasedOnNode'])->name('products.products.node');
|
Route::get('/products/products/{egg?}/{location?}', [FrontProductController::class, 'getProductsBasedOnLocation'])->name('products.products.location');
|
||||||
|
|
||||||
//payments
|
//payments
|
||||||
Route::get('checkout/{shopProduct}', [PaymentController::class, 'checkOut'])->name('checkout');
|
Route::get('checkout/{shopProduct}', [PaymentController::class, 'checkOut'])->name('checkout');
|
||||||
|
|
|
@ -133,32 +133,25 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="node">{{ __('Node') }}</label>
|
<label for="location">{{ __('Location') }}</label>
|
||||||
<select name="node" required id="node" x-model="selectedNode"
|
<select name="location" required id="location" x-model="selectedLocation" :disabled="!fetchedLocations"
|
||||||
:disabled="!fetchedLocations" @change="fetchProducts();" class="custom-select">
|
@change="fetchProducts();" class="custom-select">
|
||||||
<option x-text="getNodeInputText()" disabled selected hidden value="null">
|
<option x-text="getLocationInputText()" disabled selected hidden value="null">
|
||||||
|
</option>
|
||||||
|
|
||||||
|
<template x-for="location in locations" :key="location.id">
|
||||||
|
<option x-text="location.name" :value="location.id">
|
||||||
</option>
|
</option>
|
||||||
|
</template>
|
||||||
<template x-for="location in locations" :key="location.id">
|
|
||||||
<optgroup :label="location.name">
|
|
||||||
|
|
||||||
<template x-for="node in location.nodes" :key="node.id">
|
|
||||||
<option x-text="node.name" :value="node.id">
|
|
||||||
|
|
||||||
</option>
|
|
||||||
</template>
|
|
||||||
</optgroup>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-100"></div>
|
<div class="w-100"></div>
|
||||||
<div class="col" x-show="selectedNode != null">
|
<div class="col" x-show="selectedLocation != null">
|
||||||
<div class="row mt-4 justify-content-center">
|
<div class="row mt-4 justify-content-center">
|
||||||
<template x-for="product in products" :key="product.id">
|
<template x-for="product in products" :key="product.id">
|
||||||
<div class="card col-xl-3 col-lg-3 col-md-4 col-sm-10 mr-2 ml-2 ">
|
<div class="card col-xl-3 col-lg-3 col-md-4 col-sm-10 mr-2 ml-2 ">
|
||||||
|
@ -248,7 +241,7 @@
|
||||||
product.doesNotFit == true ||
|
product.doesNotFit == true ||
|
||||||
submitClicked ? 'disabled' : ''"
|
submitClicked ? 'disabled' : ''"
|
||||||
class="btn btn-primary btn-block mt-2" @click="setProduct(product.id);"
|
class="btn btn-primary btn-block mt-2" @click="setProduct(product.id);"
|
||||||
x-text="product.doesNotFit == true ? '{{ __('Server cant fit on this Node') }}' : (product.minimum_credits > user.credits || product.price > user.credits ? '{{ __('Not enough') }} {{ $credits_display_name }}!' : '{{ __('Create server') }}')">
|
x-text="product.doesNotFit == true ? '{{ __('Server cant fit on this Location') }}' : (product.minimum_credits > user.credits || product.price > user.credits ? '{{ __('Not enough') }} {{ $credits_display_name }}!' : '{{ __('Create server') }}')">
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -278,13 +271,13 @@
|
||||||
name: null,
|
name: null,
|
||||||
selectedNest: null,
|
selectedNest: null,
|
||||||
selectedEgg: null,
|
selectedEgg: null,
|
||||||
selectedNode: null,
|
selectedLocation: null,
|
||||||
selectedProduct: null,
|
selectedProduct: null,
|
||||||
|
|
||||||
//selected objects based on input
|
//selected objects based on input
|
||||||
selectedNestObject: {},
|
selectedNestObject: {},
|
||||||
selectedEggObject: {},
|
selectedEggObject: {},
|
||||||
selectedNodeObject: {},
|
selectedLocationObject: {},
|
||||||
selectedProductObject: {},
|
selectedProductObject: {},
|
||||||
|
|
||||||
//values
|
//values
|
||||||
|
@ -309,7 +302,7 @@
|
||||||
this.locations = [];
|
this.locations = [];
|
||||||
this.products = [];
|
this.products = [];
|
||||||
this.selectedEgg = 'null';
|
this.selectedEgg = 'null';
|
||||||
this.selectedNode = 'null';
|
this.selectedLocation = 'null';
|
||||||
this.selectedProduct = 'null';
|
this.selectedProduct = 'null';
|
||||||
|
|
||||||
this.eggs = this.eggsSave.filter(egg => egg.nest_id == this.selectedNest)
|
this.eggs = this.eggsSave.filter(egg => egg.nest_id == this.selectedNest)
|
||||||
|
@ -343,7 +336,7 @@
|
||||||
this.fetchedProducts = false;
|
this.fetchedProducts = false;
|
||||||
this.locations = [];
|
this.locations = [];
|
||||||
this.products = [];
|
this.products = [];
|
||||||
this.selectedNode = 'null';
|
this.selectedLocation = 'null';
|
||||||
this.selectedProduct = 'null';
|
this.selectedProduct = 'null';
|
||||||
|
|
||||||
let response = await axios.get(`{{ route('products.locations.egg') }}/${this.selectedEgg}`)
|
let response = await axios.get(`{{ route('products.locations.egg') }}/${this.selectedEgg}`)
|
||||||
|
@ -354,7 +347,7 @@
|
||||||
|
|
||||||
//automatically select the first entry if there is only 1
|
//automatically select the first entry if there is only 1
|
||||||
if (this.locations.length === 1 && this.locations[0]?.nodes?.length === 1) {
|
if (this.locations.length === 1 && this.locations[0]?.nodes?.length === 1) {
|
||||||
this.selectedNode = this.locations[0]?.nodes[0]?.id;
|
this.selectedLocation = this.locations[0]?.id;
|
||||||
await this.fetchProducts();
|
await this.fetchProducts();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -366,7 +359,7 @@
|
||||||
/**
|
/**
|
||||||
* @description fetch all available products based on the selected node
|
* @description fetch all available products based on the selected node
|
||||||
* @note called whenever a node is selected
|
* @note called whenever a node is selected
|
||||||
* @see selectedNode
|
* @see selectedLocation
|
||||||
*/
|
*/
|
||||||
async fetchProducts() {
|
async fetchProducts() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
@ -375,7 +368,7 @@
|
||||||
this.selectedProduct = 'null';
|
this.selectedProduct = 'null';
|
||||||
|
|
||||||
let response = await axios.get(
|
let response = await axios.get(
|
||||||
`{{ route('products.products.node') }}/${this.selectedEgg}/${this.selectedNode}`)
|
`{{ route('products.products.location') }}/${this.selectedEgg}/${this.selectedLocation}`)
|
||||||
.catch(console.error)
|
.catch(console.error)
|
||||||
|
|
||||||
this.fetchedProducts = true;
|
this.fetchedProducts = true;
|
||||||
|
@ -410,10 +403,10 @@
|
||||||
this.selectedNestObject = this.nests.find(nest => nest.id == this.selectedNest) ?? {}
|
this.selectedNestObject = this.nests.find(nest => nest.id == this.selectedNest) ?? {}
|
||||||
this.selectedEggObject = this.eggs.find(egg => egg.id == this.selectedEgg) ?? {}
|
this.selectedEggObject = this.eggs.find(egg => egg.id == this.selectedEgg) ?? {}
|
||||||
|
|
||||||
this.selectedNodeObject = {};
|
this.selectedLocationObject = {};
|
||||||
this.locations.forEach(location => {
|
this.locations.forEach(location => {
|
||||||
if (!this.selectedNodeObject?.id) {
|
if (!this.selectedLocationObject?.id) {
|
||||||
this.selectedNodeObject = location.nodes.find(node => node.id == this.selectedNode) ??
|
this.selectedLocationObject = location.nodes.find(node => node.id == this.selectedLocation) ??
|
||||||
{};
|
{};
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -429,17 +422,17 @@
|
||||||
isFormValid() {
|
isFormValid() {
|
||||||
if (Object.keys(this.selectedNestObject).length === 0) return false;
|
if (Object.keys(this.selectedNestObject).length === 0) return false;
|
||||||
if (Object.keys(this.selectedEggObject).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.selectedLocationObject).length === 0) return false;
|
||||||
if (Object.keys(this.selectedProductObject).length === 0) return false;
|
if (Object.keys(this.selectedProductObject).length === 0) return false;
|
||||||
return !!this.name;
|
return !!this.name;
|
||||||
},
|
},
|
||||||
|
|
||||||
getNodeInputText() {
|
getLocationInputText() {
|
||||||
if (this.fetchedLocations) {
|
if (this.fetchedLocations) {
|
||||||
if (this.locations.length > 0) {
|
if (this.locations.length > 0) {
|
||||||
return '{{ __('Please select a node ...') }}';
|
return '{{ __('Please select a location ...') }}';
|
||||||
}
|
}
|
||||||
return '{{ __('No nodes found matching current configuration') }}'
|
return '{{ __('No location found matching current configuration') }}'
|
||||||
}
|
}
|
||||||
return '{{ __('---') }}';
|
return '{{ __('---') }}';
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue