Merge pull request #315 from IceToast/servercreation_resouce_card

servers/create - updated UI
This commit is contained in:
Dennis 2021-12-20 22:56:49 +01:00 committed by GitHub
commit 1858cf0db2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 736 additions and 199 deletions

29
public/css/app.css vendored

File diff suppressed because one or more lines are too long

569
public/js/app.js vendored

File diff suppressed because one or more lines are too long

View file

@ -24,41 +24,41 @@
<!-- MAIN CONTENT -->
<section x-data="serverApp()" class="content">
<div class="container">
<div class="container-xxl">
<!-- FORM -->
<form action="{{route('servers.store')}}" method="post" class="row">
<form action="{{ route('servers.store') }}" method="post" class="row justify-content-center">
@csrf
<div class="col-md-8">
<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 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 )
@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>
<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')}}
@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') }}
@endif
</p>
<ul>
@if($productCount === 0 )
<li> {{__('No products available!')}}</li>
@if ($productCount === 0)
<li> {{ __('No products available!') }}</li>
@endif
@if($nodeCount === 0 )
<li>{{__('No nodes have been linked!')}}</li>
@if ($nodeCount === 0)
<li>{{ __('No nodes have been linked!') }}</li>
@endif
@if(count($nests) === 0 )
<li>{{__('No nests available!')}}</li>
@if (count($nests) === 0)
<li>{{ __('No nests available!') }}</li>
@endif
@if(count($eggs) === 0 )
<li>{{__('No eggs have been linked!')}}</li>
@if (count($eggs) === 0)
<li>{{ __('No eggs have been linked!') }}</li>
@endif
</ul>
</div>
@ -68,8 +68,8 @@
<div x-show="loading" class="overlay dark">
<i class="fas fa-2x fa-sync-alt"></i>
</div>
<div class="card-body">
<div class="card-body">
@if ($errors->any())
<div class="alert alert-danger">
<ul class="list-group pl-3">
@ -81,29 +81,25 @@
@endif
<div class="form-group">
<label for="name">{{__('Name')}}</label>
<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">
class="form-control @error('name') is-invalid @enderror">
@error('name')
<div class="invalid-feedback">
{{ $message }}
</div>
<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>
<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
@ -111,21 +107,15 @@
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="egg">{{__('Specification')}}</label>
<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>
<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>
@ -136,25 +126,17 @@
</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="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 x-text="node.name" :value="node.id">
</option>
</template>
@ -163,127 +145,90 @@
</select>
</div>
<div class="form-group">
<label for="product">{{__('Resources')}}</label>
<select name="product"
required
id="product"
:disabled="!fetchedProducts"
x-model="selectedProduct"
@change="updateSelectedObjects()"
class="custom-select">
<option
x-text="getProductInputText()"
disabled selected hidden value="null"></option>
<template x-for="product in products" :key="product.id">
<option :disabled="product.minimum_credits > user.credits"
x-text="getProductOptionText(product)"
:value="product.id">
</option>
</template>
</select>
</div>
</div>
</div>
</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 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 + ' %'"></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" 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>
<button type="submit" x-model="selectedProduct" name="product"
:disabled="product.minimum_credits > user.credits"
:class="product.minimum_credits > user.credits ? 'disabled' : ''"
class="btn btn-primary btn-block mt-2" @click="setProduct(product.id)"
x-text=" product.minimum_credits > user.credits ? '{{ __('Not enough') }} {{ CREDITS_DISPLAY_NAME }}!' : {{ __('Create server') }}">
</button>
</div>
</div>
</div>
<div class="card-body">
<ul class="list-group mb-3">
<li x-show="selectedNestObject.name"
class="list-group-item d-flex justify-content-between lh-condensed">
<div>
<h6 class="my-0">{{__('Software / Games')}}</h6>
<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">{{__('Specification')}}</h6>
<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 x-text="selectedNodeObject?.name ?? '{{__('No selection')}}'"
class="text-muted"></small>
</div>
</li>
<li class="list-group-item">
<div>
<h6 class="my-0">{{__('Resources')}}</h6>
<small x-text="selectedProductObject?.name ?? '{{__('No selection')}}'"
class="text-muted"></small>
</div>
<template x-if="selectedProductObject?.name">
<ul class="pl-0">
<li class="d-flex justify-content-between">
<small class="text-muted d-inline-block">{{__('Cpu')}}</small>
<small class="text-muted d-inline-block"
x-text="selectedProductObject.cpu + ' %'"></small>
</li>
<div class="d-flex justify-content-between">
<small class="text-muted d-inline-block">{{__('Memory')}}</small>
<small class="text-muted d-inline-block"
x-text="selectedProductObject.memory + ' {{__('MB')}}'"></small>
</div>
<div class="d-flex justify-content-between">
<small class="text-muted d-inline-block">{{__('Disk')}}</small>
<small class="text-muted d-inline-block"
x-text="selectedProductObject.disk + ' {{__('MB')}}'"></small>
</div>
<div class="d-flex justify-content-between">
<small class="text-muted d-inline-block">{{__('Databases')}}</small>
<small class="text-muted d-inline-block"
x-text="selectedProductObject.databases + ' {{__('MySQL')}}'"></small>
</div>
<div class="d-flex justify-content-between">
<small class="text-muted d-inline-block">{{__('Backups')}}</small>
<small class="text-muted d-inline-block"
x-text="selectedProductObject.backups"></small>
</div>
<div class="d-flex justify-content-between">
<small class="text-muted d-inline-block">{{__('Allocations')}}</small>
<small class="text-muted d-inline-block" x-text="selectedProductObject.allocations"></small>
</div>
</ul>
</template>
</li>
</ul>
<ul class="list-group mb-3">
<li class="list-group-item d-flex justify-content-between">
<span>{{CREDITS_DISPLAY_NAME}} {{__('per month')}}</span>
<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>
</template>
</div>
</form>
<!-- END FORM -->
</div>
</form>
<!-- END FORM -->
</div>
</section>
@ -314,7 +259,7 @@
//values
user: {!! $user !!},
nests: {!! $nests !!},
eggsSave:{!! $eggs !!}, //store back-end eggs
eggsSave: {!! $eggs !!}, //store back-end eggs
eggs: [],
locations: [],
products: [],
@ -346,6 +291,14 @@
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
@ -360,7 +313,7 @@
this.selectedNode = '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}`)
.catch(console.error)
this.fetchedLocations = true;
@ -388,16 +341,14 @@
this.products = [];
this.selectedProduct = 'null';
let response = await axios.get(`{{route('products.products.node')}}/${this.selectedEgg}/${this.selectedNode}`)
let response = await axios.get(
`{{ route('products.products.node') }}/${this.selectedEgg}/${this.selectedNode}`)
.catch(console.error)
this.fetchedProducts = true;
this.products = response.data
// TODO: Sortable by user chosen property (cpu, ram, disk...)
this.products = response.data.sort((p1, p2) => p1.price > p2.price && 1 || -1 )
//automatically select the first entry if there is only 1
if (this.products.length === 1) {
this.selectedProduct = this.products[0].id;
}
this.loading = false;
this.updateSelectedObjects()
@ -414,11 +365,13 @@
this.selectedNodeObject = {};
this.locations.forEach(location => {
if (!this.selectedNodeObject?.id) {
this.selectedNodeObject = location.nodes.find(node => node.id == this.selectedNode) ?? {};
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)
},
/**
@ -436,35 +389,35 @@
getNodeInputText() {
if (this.fetchedLocations) {
if (this.locations.length > 0) {
return '{{__('Please select a node ...')}}';
return '{{ __('Please select a node ...') }}';
}
return '{{__('No nodes found matching current configuration')}}'
return '{{ __('No nodes found matching current configuration') }}'
}
return '{{__('---')}}';
return '{{ __('---') }}';
},
getProductInputText() {
if (this.fetchedProducts) {
if (this.products.length > 0) {
return '{{__('Please select a resource ...')}}';
return '{{ __('Please select a resource ...') }}';
}
return '{{__('No resources found matching current configuration')}}'
return '{{ __('No resources found matching current configuration') }}'
}
return '{{__('---')}}';
return '{{ __('---') }}';
},
getEggInputText() {
if (this.selectedNest) {
return '{{__('Please select a configuration ...')}}';
return '{{ __('Please select a configuration ...') }}';
}
return '{{__('---')}}';
return '{{ __('---') }}';
},
getProductOptionText(product) {
let text = product.name + ' (' + product.description + ')';
if (product.minimum_credits > this.user.credits) {
return '{{__('Not enough credits!')}} | ' + text;
return '{{ __('Not enough credits!') }} | ' + text;
}
return text;