ctrlpanel/resources/views/servers/create.blade.php
2023-01-02 15:28:59 +01:00

445 lines
22 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@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>{{ __('Servers') }}</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 href="{{ route('servers.index') }}">{{ __('Servers') }}</a>
<li class="breadcrumb-item"><a class="text-muted"
href="{{ route('servers.create') }}">{{ __('Create') }}</a>
</li>
</ol>
</div>
</div>
</div>
</section>
<!-- END CONTENT HEADER -->
<!-- MAIN CONTENT -->
<section x-data="serverApp()" class="content">
<div class="container-xxl">
<!-- FORM -->
<form action="{{ route('servers.store') }}" method="post" class="row justify-content-center">
@csrf
<div class="col-xl-6 col-lg-8 col-md-8 col-sm-10">
<div class="card">
<div class="card-header">
<div class="card-title"><i class="fas fa-cogs mr-2"></i>{{ __('Server configuration') }}
</div>
</div>
@if (!config("SETTINGS::SYSTEM:CREATION_OF_NEW_SERVERS"))
<div class="alert alert-warning p-2 m-2">
The creation of new servers has been disabled for regular users, enable it again
<a href="{{route('admin.settings.system')}}">{{ __('here') }}</a>.
</div>
@endif
@if ($productCount === 0 || $nodeCount === 0 || count($nests) === 0 || count($eggs) === 0)
<div class="alert alert-danger p-2 m-2">
<h5><i class="icon fas fa-exclamation-circle"></i>{{ __('Error!') }}</h5>
<p class="pl-4">
@if (Auth::user()->role == 'admin')
{{ __('Make sure to link your products to nodes and eggs.') }} <br>
{{ __('There has to be at least 1 valid product for server creation') }}
<a href="{{route('admin.overview.sync')}}">{{ __('Sync now') }}</a>
@endif
</p>
<ul>
@if ($productCount === 0)
<li> {{ __('No products available!') }}</li>
@endif
@if ($nodeCount === 0)
<li>{{ __('No nodes have been linked!') }}</li>
@endif
@if (count($nests) === 0)
<li>{{ __('No nests available!') }}</li>
@endif
@if (count($eggs) === 0)
<li>{{ __('No eggs have been linked!') }}</li>
@endif
</ul>
</div>
@endif
<div x-show="loading" class="overlay dark">
<i class="fas fa-2x fa-sync-alt"></i>
</div>
<div class="card-body">
@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"
class="form-control @error('name') is-invalid @enderror">
@error('name')
<div class="invalid-feedback">
{{ $message }}
</div>
@enderror
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="nest">{{ __('Software / Games') }}</label>
<select class="custom-select" required name="nest" id="nest" x-model="selectedNest"
@change="setEggs();">
<option selected disabled hidden value="null">
{{ count($nests) > 0 ? __('Please select software ...') : __('---') }}
</option>
@foreach ($nests as $nest)
<option value="{{ $nest->id }}">{{ $nest->name }}</option>
@endforeach
</select>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="egg">{{ __('Specification ') }}</label>
<div>
<select id="egg" required name="egg" :disabled="eggs.length == 0"
x-model="selectedEgg" @change="fetchLocations();" required="required"
class="custom-select">
<option x-text="getEggInputText()" selected disabled hidden value="null">
</option>
<template x-for="egg in eggs" :key="egg.id">
<option x-text="egg.name" :value="egg.id"></option>
</template>
</select>
</div>
</div>
</div>
</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">
</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>
</template>
</optgroup>
</template>
</select>
</div>
</div>
</div>
</div>
<div class="w-100"></div>
<div class="col" x-show="selectedNode != 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 ">
<div class="card-body d-flex flex-column">
<h4 class="card-title" x-text="product.name"></h4>
<div class="mt-2">
<div>
<p class="card-text text-muted mb-1">{{ __('Resource Data:') }}</p>
<ul class="pl-0">
<li class="d-flex justify-content-between">
<span class="d-inline-block"><i class="fas fa-microchip"></i>
{{ __('CPU') }}</span>
<span class=" d-inline-block"
x-text="product.cpu + ' {{ __('vCores') }}'"></span>
</li>
<li class="d-flex justify-content-between">
<span class="d-inline-block"><i class="fas fa-memory"></i>
{{ __('Memory') }}</span>
<span class=" d-inline-block"
x-text="product.memory + ' {{ __('MB') }}'"></span>
</li>
<li class="d-flex justify-content-between">
<div>
<i class="fas fa-hdd"></i>
<span class="d-inline-block">
{{ __('Disk') }}
</span>
</div>
<span class="d-inline-block"
x-text="product.disk + ' {{ __('MB') }}'"></span>
</li>
<li class="d-flex justify-content-between">
<span class="d-inline-block"><i class="fas fa-save"></i>
{{ __('Backups') }}</span>
<span class=" d-inline-block" x-text="product.backups"></span>
</li>
<li class="d-flex justify-content-between">
<span class="d-inline-block"><i class="fas fa-database"></i>
{{ __('MySQL') }}
{{ __('Databases') }}</span>
<span class="d-inline-block" x-text="product.databases"></span>
</li>
<li class="d-flex justify-content-between">
<span class="d-inline-block"><i class="fas fa-network-wired"></i>
{{ __('Allocations') }}
({{ __('ports') }})</span>
<span class="d-inline-block" x-text="product.allocations"></span>
</li>
</ul>
</div>
<div class="mt-2 mb-2">
<span class="card-text text-muted">{{ __('Description') }}</span>
<p class="card-text" style="white-space:pre-wrap" x-text="product.description"></p>
</div>
</div>
<div class="mt-auto border rounded border-secondary">
<div class="d-flex justify-content-between p-2">
<span class="d-inline-block mr-4">
{{ __('Price') }}:
</span>
<span class="d-inline-block"
x-text="product.price + ' {{ CREDITS_DISPLAY_NAME }}'"></span>
</div>
</div>
<div x-data="{ buttonDisabled: false }">
<button type="submit" x-model="selectedProduct" name="product"
:disabled="product.minimum_credits > user.credits||product.doesNotFit == true"
:class="product.minimum_credits > user.credits ? 'disabled' : ''"
class="btn btn-primary btn-block mt-2" @click="setProduct(product.id)"
x-text=" product.doesNotFit == true? '{{ __('Server can´t fit on this node') }}' : (product.minimum_credits > user.credits ? '{{ __('Not enough') }} {{ CREDITS_DISPLAY_NAME }}!' : '{{ __('Create server') }}')">
</button>
</div>
</div>
</div>
</div>
</template>
</div>
</div>
</form>
<!-- END FORM -->
</div>
</section>
<!-- END CONTENT -->
<script>
function serverApp() {
return {
//loading
loading: false,
fetchedLocations: 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
user: {!! $user !!},
nests: {!! $nests !!},
eggsSave: {!! $eggs !!}, //store back-end eggs
eggs: [],
locations: [],
products: [],
/**
* @description set available eggs based on the selected nest
* @note called whenever a nest is selected
* @see selectedNest
*/
async setEggs() {
this.fetchedLocations = false;
this.fetchedProducts = false;
this.locations = [];
this.products = [];
this.selectedEgg = 'null';
this.selectedNode = 'null';
this.selectedProduct = 'null';
this.eggs = this.eggsSave.filter(egg => egg.nest_id == this.selectedNest)
//automatically select the first entry if there is only 1
if (this.eggs.length === 1) {
this.selectedEgg = this.eggs[0].id;
await this.fetchLocations();
return;
}
this.updateSelectedObjects()
},
setProduct(productId) {
if (!productId) return
this.selectedProduct = productId;
this.updateSelectedObjects();
},
/**
* @description fetch all available locations based on the selected egg
* @note called whenever a server configuration is selected
* @see selectedEg
*/
async fetchLocations() {
this.loading = true;
this.fetchedLocations = false;
this.fetchedProducts = false;
this.locations = [];
this.products = [];
this.selectedNode = 'null';
this.selectedProduct = 'null';
let response = await axios.get(`{{ route('products.locations.egg') }}/${this.selectedEgg}`)
.catch(console.error)
this.fetchedLocations = true;
this.locations = response.data
//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;
await this.fetchProducts();
return;
}
this.loading = false;
this.updateSelectedObjects()
},
/**
* @description fetch all available products based on the selected node
* @note called whenever a node is selected
* @see selectedNode
*/
async fetchProducts() {
this.loading = true;
this.fetchedProducts = false;
this.products = [];
this.selectedProduct = 'null';
let response = await axios.get(
`{{ route('products.products.node') }}/${this.selectedEgg}/${this.selectedNode}`)
.catch(console.error)
this.fetchedProducts = true;
// TODO: Sortable by user chosen property (cpu, ram, disk...)
this.products = response.data.sort((p1, p2) => parseInt(p1.price,10) > parseInt(p2.price,10) && 1 || -1)
//divide cpu by 100 for each product
this.products.forEach(product => {
product.cpu = product.cpu / 100;
})
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.locations.forEach(location => {
if (!this.selectedNodeObject?.id) {
this.selectedNodeObject = location.nodes.find(node => node.id == this.selectedNode) ??
{};
}
})
this.selectedProductObject = this.products.find(product => product.id == this.selectedProduct) ?? {}
console.log(this.selectedProduct, this.selectedProductObject, this.products)
},
/**
* @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() {
if (this.fetchedLocations) {
if (this.locations.length > 0) {
return '{{ __('Please select a node ...') }}';
}
return '{{ __('No nodes found matching current configuration') }}'
}
return '{{ __('---') }}';
},
getProductInputText() {
if (this.fetchedProducts) {
if (this.products.length > 0) {
return '{{ __('Please select a resource ...') }}';
}
return '{{ __('No resources found matching current configuration') }}'
}
return '{{ __('---') }}';
},
getEggInputText() {
if (this.selectedNest) {
return '{{ __('Please select a configuration ...') }}';
}
return '{{ __('---') }}';
},
getProductOptionText(product) {
let text = product.name + ' (' + product.description + ')';
if (product.minimum_credits > this.user.credits) {
return '{{ __('Not enough credits!') }} | ' + text;
}
return text;
}
}
}
</script>
@endsection