[Feature] Addon implemented
This commit is contained in:
parent
159ba02c84
commit
130a3b308b
|
@ -97,32 +97,43 @@ class ProductController extends Controller
|
|||
}
|
||||
|
||||
/**
|
||||
* @param Node $node
|
||||
* @param Int $location
|
||||
* @param Egg $egg
|
||||
* @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)) {
|
||||
return response()->json('node and egg id is required', '400');
|
||||
if (is_null($egg->id) || is_null($location)) {
|
||||
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()
|
||||
->where('disabled', '=', false)
|
||||
->whereHas('nodes', function (Builder $builder) use ($node) {
|
||||
$builder->where('id', '=', $node->id);
|
||||
->whereHas('nodes', function (Builder $builder) use ($nodes) {
|
||||
$builder->whereIn('id', $nodes->map(function ($node) {
|
||||
return $node->id;
|
||||
}));
|
||||
})
|
||||
->whereHas('eggs', function (Builder $builder) use ($egg) {
|
||||
$builder->where('id', '=', $egg->id);
|
||||
})
|
||||
->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) {
|
||||
$product->doesNotFit = false;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $products;
|
||||
}
|
||||
|
|
|
@ -141,13 +141,10 @@ class ServerController extends Controller
|
|||
$product = Product::findOrFail(FacadesRequest::input('product'));
|
||||
|
||||
// Get node resource allocation info
|
||||
$node = $product->nodes()->findOrFail(FacadesRequest::input('node'));
|
||||
$nodeName = $node->name;
|
||||
|
||||
// Check if node has enough memory and disk space
|
||||
$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."));
|
||||
$location = FacadesRequest::input('location');
|
||||
$availableNode = $this->getAvailableNode($location, $product);
|
||||
if (!$availableNode) {
|
||||
return redirect()->route('servers.index')->with('error', __("The chosen location doesn't have the required memory or disk left to allocate this product."));
|
||||
}
|
||||
|
||||
// Min. Credits
|
||||
|
@ -179,7 +176,7 @@ class ServerController extends Controller
|
|||
/** Store a newly created resource in storage. */
|
||||
public function store(Request $request, UserSettings $user_settings, ServerSettings $server_settings, GeneralSettings $generalSettings)
|
||||
{
|
||||
/** @var Node $node */
|
||||
/** @var Location $location */
|
||||
/** @var Egg $egg */
|
||||
/** @var Product $product */
|
||||
$validate_configuration = $this->validateConfigurationRules($user_settings, $server_settings, $generalSettings);
|
||||
|
@ -190,15 +187,23 @@ class ServerController extends Controller
|
|||
|
||||
$request->validate([
|
||||
'name' => 'required|max:191',
|
||||
'node' => 'required|exists:nodes,id',
|
||||
'location' => 'required|exists:locations,id',
|
||||
'egg' => 'required|exists:eggs,id',
|
||||
'product' => 'required|exists:products,id',
|
||||
]);
|
||||
|
||||
//get required resources
|
||||
// Get the product and egg
|
||||
$product = Product::query()->findOrFail($request->input('product'));
|
||||
$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([
|
||||
'name' => $request->input('name'),
|
||||
|
@ -316,7 +321,7 @@ class ServerController extends Controller
|
|||
})
|
||||
->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) {
|
||||
$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']) {
|
||||
|
@ -356,8 +361,8 @@ class ServerController extends Controller
|
|||
// Check if node has enough memory and disk space
|
||||
$requireMemory = $newProduct->memory - $oldProduct->memory;
|
||||
$requiredisk = $newProduct->disk - $oldProduct->disk;
|
||||
$checkResponse = $this->pterodactyl->checkNodeResources($node, $requireMemory, $requiredisk);
|
||||
if ($checkResponse == false) {
|
||||
$nodeFree = $this->pterodactyl->checkNodeResources($node, $requireMemory, $requiredisk);
|
||||
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."));
|
||||
}
|
||||
|
||||
|
@ -412,4 +417,30 @@ class ServerController extends Controller
|
|||
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",
|
||||
"Profile updated": "Profile updated",
|
||||
"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 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",
|
||||
|
|
|
@ -95,7 +95,7 @@ Route::middleware(['auth', 'checkSuspended'])->group(function () {
|
|||
//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/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
|
||||
Route::get('checkout/{shopProduct}', [PaymentController::class, 'checkOut'])->name('checkout');
|
||||
|
|
|
@ -134,23 +134,16 @@
|
|||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="node">{{ __('Node') }}</label>
|
||||
<select name="node" required id="node" x-model="selectedNode"
|
||||
:disabled="!fetchedLocations" @change="fetchProducts();" class="custom-select">
|
||||
<option x-text="getNodeInputText()" disabled selected hidden value="null">
|
||||
<label for="location">{{ __('Location') }}</label>
|
||||
<select name="location" required id="location" x-model="selectedLocation" :disabled="!fetchedLocations"
|
||||
@change="fetchProducts();" class="custom-select">
|
||||
<option x-text="getLocationInputText()" disabled selected hidden value="null">
|
||||
</option>
|
||||
|
||||
<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 x-text="location.name" :value="location.id">
|
||||
</option>
|
||||
</template>
|
||||
</optgroup>
|
||||
</template>
|
||||
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -158,7 +151,7 @@
|
|||
</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">
|
||||
<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 ">
|
||||
|
@ -248,7 +241,7 @@
|
|||
product.doesNotFit == true ||
|
||||
submitClicked ? 'disabled' : ''"
|
||||
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>
|
||||
</div>
|
||||
|
||||
|
@ -278,13 +271,13 @@
|
|||
name: null,
|
||||
selectedNest: null,
|
||||
selectedEgg: null,
|
||||
selectedNode: null,
|
||||
selectedLocation: null,
|
||||
selectedProduct: null,
|
||||
|
||||
//selected objects based on input
|
||||
selectedNestObject: {},
|
||||
selectedEggObject: {},
|
||||
selectedNodeObject: {},
|
||||
selectedLocationObject: {},
|
||||
selectedProductObject: {},
|
||||
|
||||
//values
|
||||
|
@ -309,7 +302,7 @@
|
|||
this.locations = [];
|
||||
this.products = [];
|
||||
this.selectedEgg = 'null';
|
||||
this.selectedNode = 'null';
|
||||
this.selectedLocation = 'null';
|
||||
this.selectedProduct = 'null';
|
||||
|
||||
this.eggs = this.eggsSave.filter(egg => egg.nest_id == this.selectedNest)
|
||||
|
@ -343,7 +336,7 @@
|
|||
this.fetchedProducts = false;
|
||||
this.locations = [];
|
||||
this.products = [];
|
||||
this.selectedNode = 'null';
|
||||
this.selectedLocation = 'null';
|
||||
this.selectedProduct = 'null';
|
||||
|
||||
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
|
||||
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();
|
||||
return;
|
||||
}
|
||||
|
@ -366,7 +359,7 @@
|
|||
/**
|
||||
* @description fetch all available products based on the selected node
|
||||
* @note called whenever a node is selected
|
||||
* @see selectedNode
|
||||
* @see selectedLocation
|
||||
*/
|
||||
async fetchProducts() {
|
||||
this.loading = true;
|
||||
|
@ -375,7 +368,7 @@
|
|||
this.selectedProduct = 'null';
|
||||
|
||||
let response = await axios.get(
|
||||
`{{ route('products.products.node') }}/${this.selectedEgg}/${this.selectedNode}`)
|
||||
`{{ route('products.products.location') }}/${this.selectedEgg}/${this.selectedLocation}`)
|
||||
.catch(console.error)
|
||||
|
||||
this.fetchedProducts = true;
|
||||
|
@ -410,10 +403,10 @@
|
|||
this.selectedNestObject = this.nests.find(nest => nest.id == this.selectedNest) ?? {}
|
||||
this.selectedEggObject = this.eggs.find(egg => egg.id == this.selectedEgg) ?? {}
|
||||
|
||||
this.selectedNodeObject = {};
|
||||
this.selectedLocationObject = {};
|
||||
this.locations.forEach(location => {
|
||||
if (!this.selectedNodeObject?.id) {
|
||||
this.selectedNodeObject = location.nodes.find(node => node.id == this.selectedNode) ??
|
||||
if (!this.selectedLocationObject?.id) {
|
||||
this.selectedLocationObject = location.nodes.find(node => node.id == this.selectedLocation) ??
|
||||
{};
|
||||
}
|
||||
})
|
||||
|
@ -429,17 +422,17 @@
|
|||
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.selectedLocationObject).length === 0) return false;
|
||||
if (Object.keys(this.selectedProductObject).length === 0) return false;
|
||||
return !!this.name;
|
||||
},
|
||||
|
||||
getNodeInputText() {
|
||||
getLocationInputText() {
|
||||
if (this.fetchedLocations) {
|
||||
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 '{{ __('---') }}';
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue