123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485 |
- @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') }}" x-on:submit="submitClicked = true" 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 (!$server_creation_enabled)
- <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.index', "#Server") }}">{{ __('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()->hasRole("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>
- <li class="d-flex justify-content-between">
- <span class="d-inline-block"><i class="fas fa-clock"></i>
- {{ __('Billing Period') }}</span>
- <span class="d-inline-block" x-text="product.billing_period"></span>
- </li>
- <li class="d-flex justify-content-between">
- <span class="d-inline-block"><i class="fa fa-coins"></i>
- {{ __('Minimum') }} {{ $credits_display_name }}</span>
- <span class="d-inline-block"
- x-text="product.minimum_credits == -1 ? {{ $min_credits_to_make_server }} : product.minimum_credits"></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"
- x-text="'{{ __('Price') }}' + ' (' + product.billing_period + ')'">
- </span>
- <span class="d-inline-block"
- x-text="product.price + ' {{ $credits_display_name }}'"></span>
- </div>
- </div>
- <div>
- <input type="hidden" name="product" x-model="selectedProduct">
- </div>
- <div>
- <button type="submit" x-model="selectedProduct" name="product"
- :disabled="product.minimum_credits > user.credits || product.price > user.credits ||
- product.doesNotFit == true ||
- submitClicked"
- :class="product.minimum_credits > user.credits || product.price > user.credits ||
- 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') }}')">
- </button>
- @if (env('APP_ENV') == 'local' || $store_enabled)
- <template x-if="product.price > user.credits">
- <a href="{{ route('store.index') }}">
- <button type="button" class="btn btn-warning btn-block mt-2">
- {{ __('Buy more') }} {{ $credits_display_name }}
- </button>
- </a>
- </template>
- @endif
- </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: [],
- submitClicked: false,
- /**
- * @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;
- })
- //format price to have no decimals if it is a whole number
- this.products.forEach(product => {
- if (product.price % 1 === 0) {
- product.price = Math.round(product.price);
- }
- })
- 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
|