commit
0c51471e5d
53 changed files with 16026 additions and 664 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -4,6 +4,7 @@
|
|||
/storage/*.key
|
||||
/vendor
|
||||
/storage/credit_deduction_log
|
||||
storage/debugbar
|
||||
.env
|
||||
.env.testing
|
||||
.env.backup
|
||||
|
|
|
@ -11,10 +11,11 @@ use Exception;
|
|||
use Illuminate\Http\Client\PendingRequest;
|
||||
use Illuminate\Http\Client\Response;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Validation\Validator;
|
||||
|
||||
class Pterodactyl
|
||||
{
|
||||
//TODO: Extend error handling (maybe logger for more errors when debugging)
|
||||
|
||||
/**
|
||||
* @return PendingRequest
|
||||
*/
|
||||
|
@ -27,18 +28,67 @@ class Pterodactyl
|
|||
])->baseUrl(env('PTERODACTYL_URL') . '/api');
|
||||
}
|
||||
|
||||
//TODO: Extend error handling (maybe logger for more errors when debugging)
|
||||
/**
|
||||
* Get user by pterodactyl id
|
||||
* @param int $pterodactylId
|
||||
* @return mixed
|
||||
* @return Exception
|
||||
*/
|
||||
public function getUser(int $pterodactylId)
|
||||
private static function getException(): Exception
|
||||
{
|
||||
$response = self::client()->get("/application/users/{$pterodactylId}");
|
||||
return new Exception('Request Failed, is pterodactyl set-up correctly?');
|
||||
}
|
||||
|
||||
if ($response->failed()) return $response->json();
|
||||
return $response->json()['attributes'];
|
||||
/**
|
||||
* @param Nest $nest
|
||||
* @return mixed
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getEggs(Nest $nest)
|
||||
{
|
||||
$response = self::client()->get("/application/nests/{$nest->id}/eggs?include=nest,variables");
|
||||
if ($response->failed()) throw self::getException();
|
||||
return $response->json()['data'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getNodes()
|
||||
{
|
||||
$response = self::client()->get('/application/nodes');
|
||||
if ($response->failed()) throw self::getException();
|
||||
return $response->json()['data'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getNests()
|
||||
{
|
||||
$response = self::client()->get('/application/nests');
|
||||
if ($response->failed()) throw self::getException();
|
||||
return $response->json()['data'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getLocations()
|
||||
{
|
||||
$response = self::client()->get('/application/locations');
|
||||
if ($response->failed()) throw self::getException();
|
||||
return $response->json()['data'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Node $node
|
||||
* @return mixed
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getFreeAllocationId(Node $node)
|
||||
{
|
||||
return self::getFreeAllocations($node)[0]['attributes']['id'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -62,66 +112,9 @@ class Pterodactyl
|
|||
return $freeAllocations;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getNests()
|
||||
{
|
||||
$response = self::client()->get('/application/nests');
|
||||
if ($response->failed()) throw self::getException();
|
||||
return $response->json()['data'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Nest $nest
|
||||
* @return mixed
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getEggs(Nest $nest)
|
||||
{
|
||||
$response = self::client()->get("/application/nests/{$nest->id}/eggs?include=nest,variables");
|
||||
if ($response->failed()) throw self::getException();
|
||||
return $response->json()['data'];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getNodes()
|
||||
{
|
||||
$response = self::client()->get('/application/nodes');
|
||||
if ($response->failed()) throw self::getException();
|
||||
return $response->json()['data'];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getLocations()
|
||||
{
|
||||
$response = self::client()->get('/application/locations');
|
||||
if ($response->failed()) throw self::getException();
|
||||
return $response->json()['data'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Node $node
|
||||
* @return mixed
|
||||
*/
|
||||
public static function getFreeAllocationId(Node $node)
|
||||
{
|
||||
|
||||
return self::getFreeAllocations($node)[0]['attributes']['id'] ?? null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Node $node
|
||||
* @return array|mixed
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getAllocations(Node $node)
|
||||
|
@ -132,7 +125,6 @@ class Pterodactyl
|
|||
return $response->json();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param String $route
|
||||
* @return string
|
||||
|
@ -174,6 +166,7 @@ class Pterodactyl
|
|||
"default" => $allocationId
|
||||
]
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
public static function suspendServer(Server $server)
|
||||
|
@ -191,10 +184,15 @@ class Pterodactyl
|
|||
}
|
||||
|
||||
/**
|
||||
* @return Exception
|
||||
* Get user by pterodactyl id
|
||||
* @param int $pterodactylId
|
||||
* @return mixed
|
||||
*/
|
||||
private static function getException(): Exception
|
||||
public function getUser(int $pterodactylId)
|
||||
{
|
||||
return new Exception('Request Failed, is pterodactyl set-up correctly?');
|
||||
$response = self::client()->get("/application/users/{$pterodactylId}");
|
||||
|
||||
if ($response->failed()) return $response->json();
|
||||
return $response->json()['attributes'];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,11 @@ use Illuminate\Http\RedirectResponse;
|
|||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Class NestsController
|
||||
* @package App\Http\Controllers\Admin
|
||||
*/
|
||||
class NestsController extends Controller
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -14,6 +14,11 @@ use Illuminate\Http\RedirectResponse;
|
|||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Class NodeController
|
||||
* @package App\Http\Controllers\Admin
|
||||
*/
|
||||
class NodeController extends Controller
|
||||
{
|
||||
/**
|
||||
|
|
64
app/Http/Controllers/Admin/OverViewController.php
Normal file
64
app/Http/Controllers/Admin/OverViewController.php
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Egg;
|
||||
use App\Models\Location;
|
||||
use App\Models\Nest;
|
||||
use App\Models\Node;
|
||||
use App\Models\Payment;
|
||||
use App\Models\Server;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class OverViewController extends Controller
|
||||
{
|
||||
public const TTL = 86400;
|
||||
|
||||
public function index()
|
||||
{
|
||||
$userCount = Cache::remember('user:count', self::TTL, function () {
|
||||
return User::query()->count();
|
||||
});
|
||||
|
||||
$creditCount = Cache::remember('credit:count', self::TTL, function () {
|
||||
return User::query()->sum('credits');
|
||||
});
|
||||
|
||||
$paymentCount = Cache::remember('payment:count', self::TTL, function () {
|
||||
return Payment::query()->count();
|
||||
});
|
||||
|
||||
$serverCount = Cache::remember('server:count', self::TTL, function () {
|
||||
return Server::query()->count();
|
||||
});
|
||||
|
||||
$lastEgg = Egg::query()->latest('updated_at')->first();
|
||||
$syncLastUpdate = $lastEgg ? $lastEgg->updated_at->isoFormat('LLL') : __('unknown');
|
||||
|
||||
return view('admin.overview.index', [
|
||||
'serverCount' => $serverCount,
|
||||
'userCount' => $userCount,
|
||||
'paymentCount' => $paymentCount,
|
||||
'creditCount' => number_format($creditCount, 2, '.', ''),
|
||||
|
||||
'locationCount' => Location::query()->count(),
|
||||
'nodeCount' => Node::query()->count(),
|
||||
'nestCount' => Nest::query()->count(),
|
||||
'eggCount' => Egg::query()->count(),
|
||||
'syncLastUpdate' => $syncLastUpdate
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Sync locations,nodes,nests,eggs with the linked pterodactyl panel
|
||||
*/
|
||||
public function syncPterodactyl()
|
||||
{
|
||||
Node::syncNodes();
|
||||
Egg::syncEggs();
|
||||
|
||||
return redirect()->back()->with('success', __('Pterodactyl synced'));
|
||||
}
|
||||
}
|
|
@ -27,6 +27,7 @@ use PayPalHttp\HttpException;
|
|||
|
||||
class PaymentController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* @return Application|Factory|View
|
||||
*/
|
||||
|
@ -45,7 +46,10 @@ class PaymentController extends Controller
|
|||
public function checkOut(Request $request, PaypalProduct $paypalProduct)
|
||||
{
|
||||
return view('store.checkout')->with([
|
||||
'product' => $paypalProduct
|
||||
'product' => $paypalProduct,
|
||||
'taxvalue' => $paypalProduct->getTaxValue(),
|
||||
'taxpercent' => $paypalProduct->getTaxPercent(),
|
||||
'total' => $paypalProduct->getTotalPrice()
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -65,8 +69,20 @@ class PaymentController extends Controller
|
|||
"reference_id" => uniqid(),
|
||||
"description" => $paypalProduct->description,
|
||||
"amount" => [
|
||||
"value" => $paypalProduct->price,
|
||||
"currency_code" => strtoupper($paypalProduct->currency_code)
|
||||
"value" => $paypalProduct->getTotalPrice(),
|
||||
'currency_code' => strtoupper($paypalProduct->currency_code),
|
||||
'breakdown' =>[
|
||||
'item_total' =>
|
||||
[
|
||||
'currency_code' => strtoupper($paypalProduct->currency_code),
|
||||
'value' => $paypalProduct->price,
|
||||
],
|
||||
'tax_total' =>
|
||||
[
|
||||
'currency_code' => strtoupper($paypalProduct->currency_code),
|
||||
'value' => $paypalProduct->getTaxValue(),
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
|
@ -76,6 +92,8 @@ class PaymentController extends Controller
|
|||
'brand_name' => config('app.name', 'Laravel'),
|
||||
'shipping_preference' => 'NO_SHIPPING'
|
||||
]
|
||||
|
||||
|
||||
];
|
||||
|
||||
|
||||
|
@ -161,6 +179,9 @@ class PaymentController extends Controller
|
|||
'status' => $response->result->status,
|
||||
'amount' => $paypalProduct->quantity,
|
||||
'price' => $paypalProduct->price,
|
||||
'tax_value' => $paypalProduct->getTaxValue(),
|
||||
'tax_percent' => $paypalProduct->getTaxPercent(),
|
||||
'total_price' => $paypalProduct->getTotalPrice(),
|
||||
'currency_code' => $paypalProduct->currency_code,
|
||||
'payer' => json_encode($response->result->payer),
|
||||
]);
|
||||
|
@ -199,7 +220,7 @@ class PaymentController extends Controller
|
|||
*/
|
||||
public function cancel(Request $request)
|
||||
{
|
||||
return redirect()->route('store.index')->with('success', 'Payment was Cannceled');
|
||||
return redirect()->route('store.index')->with('success', 'Payment was Canceled');
|
||||
}
|
||||
|
||||
|
||||
|
@ -216,7 +237,13 @@ class PaymentController extends Controller
|
|||
return $payment->user->name;
|
||||
})
|
||||
->editColumn('price', function (Payment $payment) {
|
||||
return $payment->formatCurrency();
|
||||
return $payment->formatToCurrency($payment->price);
|
||||
})
|
||||
->editColumn('tax_value', function (Payment $payment) {
|
||||
return $payment->formatToCurrency($payment->tax_value);
|
||||
})
|
||||
->editColumn('total_price', function (Payment $payment) {
|
||||
return $payment->formatToCurrency($payment->total_price);
|
||||
})
|
||||
->editColumn('created_at', function (Payment $payment) {
|
||||
return $payment->created_at ? $payment->created_at->diffForHumans() : '';
|
||||
|
|
|
@ -62,7 +62,7 @@ class PaypalProductController extends Controller
|
|||
$disabled = !is_null($request->input('disabled'));
|
||||
PaypalProduct::create(array_merge($request->all(), ['disabled' => $disabled]));
|
||||
|
||||
return redirect()->route('admin.store.index')->with('success', 'store item has been created!');
|
||||
return redirect()->route('admin.store.index')->with('success', 'Store item has been created!');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -112,7 +112,7 @@ class PaypalProductController extends Controller
|
|||
$disabled = !is_null($request->input('disabled'));
|
||||
$paypalProduct->update(array_merge($request->all(), ['disabled' => $disabled]));
|
||||
|
||||
return redirect()->route('admin.store.index')->with('success', 'store item has been updated!');
|
||||
return redirect()->route('admin.store.index')->with('success', 'Store item has been updated!');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -124,7 +124,7 @@ class PaypalProductController extends Controller
|
|||
{
|
||||
$paypalProduct->update(['disabled' => !$paypalProduct->disabled]);
|
||||
|
||||
return redirect()->route('admin.store.index')->with('success', 'product has been updated!');
|
||||
return redirect()->route('admin.store.index')->with('success', 'Product has been updated!');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -136,7 +136,7 @@ class PaypalProductController extends Controller
|
|||
public function destroy(PaypalProduct $paypalProduct)
|
||||
{
|
||||
$paypalProduct->delete();
|
||||
return redirect()->back()->with('success', 'store item has been removed!');
|
||||
return redirect()->back()->with('success', 'Store item has been removed!');
|
||||
}
|
||||
|
||||
|
||||
|
@ -173,7 +173,7 @@ class PaypalProductController extends Controller
|
|||
return $paypalProduct->created_at ? $paypalProduct->created_at->diffForHumans() : '';
|
||||
})
|
||||
->editColumn('price', function (PaypalProduct $paypalProduct) {
|
||||
return $paypalProduct->formatCurrency();
|
||||
return $paypalProduct->formatToCurrency($paypalProduct->price);
|
||||
})
|
||||
->rawColumns(['actions', 'disabled'])
|
||||
->make();
|
||||
|
|
|
@ -3,6 +3,10 @@
|
|||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Egg;
|
||||
use App\Models\Location;
|
||||
use App\Models\Nest;
|
||||
use App\Models\Node;
|
||||
use App\Models\Configuration;
|
||||
use App\Models\Product;
|
||||
use Exception;
|
||||
|
@ -33,7 +37,18 @@ class ProductController extends Controller
|
|||
*/
|
||||
public function create()
|
||||
{
|
||||
return view('admin.products.create');
|
||||
return view('admin.products.create' , [
|
||||
'locations' => Location::with('nodes')->get(),
|
||||
'nests' => Nest::with('eggs')->get(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function clone(Request $request , Product $product){
|
||||
return view('admin.products.create' , [
|
||||
'product' => $product,
|
||||
'locations' => Location::with('nodes')->get(),
|
||||
'nests' => Nest::with('eggs')->get(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -57,13 +72,19 @@ class ProductController extends Controller
|
|||
"databases" => "required|numeric|max:1000000|min:0",
|
||||
"backups" => "required|numeric|max:1000000|min:0",
|
||||
"allocations" => "required|numeric|max:1000000|min:0",
|
||||
"nodes.*" => "required|exists:nodes,id",
|
||||
"eggs.*" => "required|exists:eggs,id",
|
||||
"disabled" => "nullable",
|
||||
]);
|
||||
|
||||
$disabled = !is_null($request->input('disabled'));
|
||||
Product::create(array_merge($request->all(), ['disabled' => $disabled]));
|
||||
$product = Product::create(array_merge($request->all(), ['disabled' => $disabled]));
|
||||
|
||||
return redirect()->route('admin.products.index')->with('success', 'product has been created!');
|
||||
#link nodes and eggs
|
||||
$product->eggs()->attach($request->input('eggs'));
|
||||
$product->nodes()->attach($request->input('nodes'));
|
||||
|
||||
return redirect()->route('admin.products.index')->with('success', 'Product has been created!');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -89,7 +110,9 @@ class ProductController extends Controller
|
|||
public function edit(Product $product)
|
||||
{
|
||||
return view('admin.products.edit', [
|
||||
'product' => $product
|
||||
'product' => $product,
|
||||
'locations' => Location::with('nodes')->get(),
|
||||
'nests' => Nest::with('eggs')->get(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -115,13 +138,21 @@ class ProductController extends Controller
|
|||
"databases" => "required|numeric|max:1000000|min:0",
|
||||
"backups" => "required|numeric|max:1000000|min:0",
|
||||
"allocations" => "required|numeric|max:1000000|min:0",
|
||||
"nodes.*" => "required|exists:nodes,id",
|
||||
"eggs.*" => "required|exists:eggs,id",
|
||||
"disabled" => "nullable",
|
||||
]);
|
||||
|
||||
$disabled = !is_null($request->input('disabled'));
|
||||
$product->update(array_merge($request->all(), ['disabled' => $disabled]));
|
||||
|
||||
return redirect()->route('admin.products.index')->with('success', 'product has been updated!');
|
||||
#link nodes and eggs
|
||||
$product->eggs()->detach();
|
||||
$product->nodes()->detach();
|
||||
$product->eggs()->attach($request->input('eggs'));
|
||||
$product->nodes()->attach($request->input('nodes'));
|
||||
|
||||
return redirect()->route('admin.products.index')->with('success', 'Product has been updated!');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -133,7 +164,7 @@ class ProductController extends Controller
|
|||
{
|
||||
$product->update(['disabled' => !$product->disabled]);
|
||||
|
||||
return redirect()->route('admin.products.index')->with('success', 'product has been updated!');
|
||||
return redirect()->route('admin.products.index')->with('success', 'Product has been updated!');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -150,7 +181,7 @@ class ProductController extends Controller
|
|||
}
|
||||
|
||||
$product->delete();
|
||||
return redirect()->back()->with('success', 'product has been removed!');
|
||||
return redirect()->back()->with('success', 'Product has been removed!');
|
||||
}
|
||||
|
||||
|
||||
|
@ -166,6 +197,7 @@ class ProductController extends Controller
|
|||
->addColumn('actions', function (Product $product) {
|
||||
return '
|
||||
<a data-content="Show" data-toggle="popover" data-trigger="hover" data-placement="top" href="' . route('admin.products.show', $product->id) . '" class="btn btn-sm text-white btn-warning mr-1"><i class="fas fa-eye"></i></a>
|
||||
<a data-content="Clone" data-toggle="popover" data-trigger="hover" data-placement="top" href="' . route('admin.products.clone', $product->id) . '" class="btn btn-sm text-white btn-primary mr-1"><i class="fas fa-clone"></i></a>
|
||||
<a data-content="Edit" data-toggle="popover" data-trigger="hover" data-placement="top" href="' . route('admin.products.edit', $product->id) . '" class="btn btn-sm btn-info mr-1"><i class="fas fa-pen"></i></a>
|
||||
|
||||
<form class="d-inline" onsubmit="return submitResult();" method="post" action="' . route('admin.products.destroy', $product->id) . '">
|
||||
|
@ -179,6 +211,12 @@ class ProductController extends Controller
|
|||
->addColumn('servers', function (Product $product) {
|
||||
return $product->servers()->count();
|
||||
})
|
||||
->addColumn('nodes', function (Product $product) {
|
||||
return $product->nodes()->count();
|
||||
})
|
||||
->addColumn('eggs', function (Product $product) {
|
||||
return $product->eggs()->count();
|
||||
})
|
||||
->addColumn('disabled', function (Product $product) {
|
||||
$checked = $product->disabled == false ? "checked" : "";
|
||||
return '
|
||||
|
|
|
@ -92,7 +92,7 @@ class ServerController extends Controller
|
|||
{
|
||||
try {
|
||||
$server->delete();
|
||||
return redirect()->route('admin.servers.index')->with('success', 'server removed');
|
||||
return redirect()->route('admin.servers.index')->with('success', 'Server removed');
|
||||
} catch (Exception $e) {
|
||||
return redirect()->route('admin.servers.index')->with('error', 'An exception has occurred while trying to remove a resource "' . $e->getMessage() . '"');
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ class ServerController extends Controller
|
|||
return redirect()->back()->with('error', $exception->getMessage());
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', 'server has been updated!');
|
||||
return redirect()->back()->with('success', 'Server has been updated!');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -8,7 +8,6 @@ use Illuminate\Contracts\View\Factory;
|
|||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class SettingsController extends Controller
|
||||
{
|
||||
|
@ -22,25 +21,20 @@ class SettingsController extends Controller
|
|||
return view('admin.settings.index');
|
||||
}
|
||||
|
||||
public function updateIcons(Request $request){
|
||||
|
||||
public function updateIcons(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'favicon' => 'required',
|
||||
'icon' => 'required',
|
||||
'icon' => 'nullable|max:10000|mimes:jpg,png,jpeg',
|
||||
'favicon' => 'nullable|max:10000|mimes:ico',
|
||||
]);
|
||||
|
||||
//store favicon
|
||||
$favicon = $request->input('favicon');
|
||||
$favicon = json_decode($favicon);
|
||||
$favicon = explode(",",$favicon->output->image)[1];
|
||||
Storage::disk('public')->put('favicon.ico' , base64_decode($favicon));
|
||||
if ($request->hasFile('icon')) {
|
||||
$request->file('icon')->storeAs('public', 'icon.png');
|
||||
}
|
||||
|
||||
//store dashboard icon
|
||||
$icon = $request->input('icon');
|
||||
$icon = json_decode($icon);
|
||||
$icon = explode(",",$icon->output->image)[1];
|
||||
|
||||
Storage::disk('public')->put('icon.png' , base64_decode($icon));
|
||||
if ($request->hasFile('favicon')) {
|
||||
$request->file('favicon')->storeAs('public', 'favicon.ico');
|
||||
}
|
||||
|
||||
return redirect()->route('admin.settings.index')->with('success', 'Icons updated!');
|
||||
}
|
||||
|
|
|
@ -250,9 +250,6 @@ class UserController extends Controller
|
|||
->addColumn('credits', function (User $user) {
|
||||
return '<i class="fas fa-coins mr-2"></i> ' . $user->credits();
|
||||
})
|
||||
->addColumn('usage', function (User $user) {
|
||||
return '<i class="fas fa-coins mr-2"></i> ' . $user->creditUsage();
|
||||
})
|
||||
->addColumn('verified', function (User $user) {
|
||||
return $user->getVerifiedStatus();
|
||||
})
|
||||
|
|
|
@ -1,16 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Egg;
|
||||
use App\Models\Product;
|
||||
use App\Models\UsefulLink;
|
||||
use App\Models\Configuration;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
|
||||
class HomeController extends Controller
|
||||
{
|
||||
const TIME_LEFT_BG_SUCCESS = "bg-success";
|
||||
const TIME_LEFT_BG_WARNING = "bg-warning";
|
||||
const TIME_LEFT_BG_DANGER = "bg-danger";
|
||||
const TIME_LEFT_BG_SUCCESS = "bg-success";
|
||||
const TIME_LEFT_BG_WARNING = "bg-warning";
|
||||
const TIME_LEFT_BG_DANGER = "bg-danger";
|
||||
const TIME_LEFT_OUT_OF_CREDITS_TEXT = "You ran out of Credits";
|
||||
|
||||
public function __construct()
|
||||
|
@ -19,55 +23,50 @@ class HomeController extends Controller
|
|||
}
|
||||
|
||||
/**
|
||||
* @description Get the Background Color for the Days-Left-Box in HomeView
|
||||
*
|
||||
* @param float $days
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTimeLeftBoxBackground(float $days)
|
||||
* @description Get the Background Color for the Days-Left-Box in HomeView
|
||||
*
|
||||
* @param float $daysLeft
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTimeLeftBoxBackground(float $daysLeft): string
|
||||
{
|
||||
switch ($days)
|
||||
{
|
||||
case ($days >= 15):
|
||||
return $this::TIME_LEFT_BG_SUCCESS;
|
||||
break;
|
||||
|
||||
case ($days >= 8 && $days <= 14):
|
||||
return $this::TIME_LEFT_BG_WARNING;
|
||||
break;
|
||||
|
||||
case ($days <= 7):
|
||||
return $this::TIME_LEFT_BG_DANGER;
|
||||
break;
|
||||
|
||||
default:
|
||||
return $this::TIME_LEFT_BG_WARNING;
|
||||
if ($daysLeft >= 15) {
|
||||
return $this::TIME_LEFT_BG_SUCCESS;
|
||||
}
|
||||
if ($daysLeft <= 7) {
|
||||
return $this::TIME_LEFT_BG_DANGER;
|
||||
}
|
||||
return $this::TIME_LEFT_BG_WARNING;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @description Set "hours", "days" or nothing behind the remaining time
|
||||
*
|
||||
* @param float $daysLeft
|
||||
* @param float $hoursLeft
|
||||
*
|
||||
* @return string|void
|
||||
*/
|
||||
public function getTimeLeftBoxUnit(float $daysLeft, float $hoursLeft)
|
||||
{
|
||||
if ($daysLeft > 1) return 'days';
|
||||
return $hoursLeft < 1 ? null : "hours";
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Get the Text for the Days-Left-Box in HomeView
|
||||
*
|
||||
* @param float $days
|
||||
* @param float $hours
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTimeLeftBoxText(float $days, float $hours)
|
||||
* @description Get the Text for the Days-Left-Box in HomeView
|
||||
*
|
||||
* @param float $daysLeft
|
||||
* @param float $hoursLeft
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTimeLeftBoxText(float $daysLeft, float $hoursLeft)
|
||||
{
|
||||
if ($days < 1)
|
||||
{
|
||||
if ($hours < 1)
|
||||
{
|
||||
return $this::TIME_LEFT_OUT_OF_CREDITS_TEXT;
|
||||
}
|
||||
else
|
||||
{
|
||||
return strval($hours);
|
||||
}
|
||||
}
|
||||
return strval(number_format($days, 0));
|
||||
if ($daysLeft > 1) return strval(number_format($daysLeft, 0));
|
||||
return ($hoursLeft < 1 ? $this::TIME_LEFT_OUT_OF_CREDITS_TEXT : strval($hoursLeft));
|
||||
}
|
||||
|
||||
/** Show the application dashboard. */
|
||||
|
@ -80,19 +79,16 @@ class HomeController extends Controller
|
|||
$unit = "";
|
||||
|
||||
/** Build our Time-Left-Box */
|
||||
if ($credits > 0.01 and $usage > 0)
|
||||
{
|
||||
$days = number_format(($credits * 30) / $usage, 2, '.', '');
|
||||
$hours = number_format($credits / ($usage / 30 / 24) , 2, '.', '');
|
||||
|
||||
$bg = $this->getTimeLeftBoxBackground($days);
|
||||
$boxText = $this->getTimeLeftBoxText($days, $hours);
|
||||
$unit = $days < 1 ? 'hours' : 'days';
|
||||
if ($credits > 0.01 and $usage > 0) {
|
||||
$daysLeft = number_format(($credits * 30) / $usage, 2, '.', '');
|
||||
$hoursLeft = number_format($credits / ($usage / 30 / 24), 2, '.', '');
|
||||
|
||||
$bg = $this->getTimeLeftBoxBackground($daysLeft);
|
||||
$boxText = $this->getTimeLeftBoxText($daysLeft, $hoursLeft);
|
||||
$unit = $daysLeft < 1 ? ($hoursLeft < 1 ? null : "hours") : "days";
|
||||
}
|
||||
|
||||
|
||||
|
||||
// RETURN ALL VALUES
|
||||
return view('home')->with([
|
||||
'useage' => $usage,
|
||||
|
|
100
app/Http/Controllers/ProductController.php
Normal file
100
app/Http/Controllers/ProductController.php
Normal file
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Egg;
|
||||
use App\Models\Location;
|
||||
use App\Models\Node;
|
||||
use App\Models\Product;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class ProductController extends Controller
|
||||
{
|
||||
/**
|
||||
* @description get product locations based on selected egg
|
||||
* @param Request $request
|
||||
* @param Egg $egg
|
||||
* @return Collection|JsonResponse
|
||||
*/
|
||||
public function getNodesBasedOnEgg(Request $request, Egg $egg)
|
||||
{
|
||||
if (is_null($egg->id)) return response()->json('Egg ID is required', '400');
|
||||
|
||||
#get products that include this egg
|
||||
$products = Product::query()
|
||||
->with('nodes')
|
||||
->where('disabled', '=', false)
|
||||
->whereHas('eggs', function (Builder $builder) use ($egg) {
|
||||
$builder->where('id', '=', $egg->id);
|
||||
})->get();
|
||||
|
||||
$nodes = collect();
|
||||
|
||||
#filter unique nodes
|
||||
$products->each(function (Product $product) use ($nodes) {
|
||||
$product->nodes->each(function (Node $node) use ($nodes) {
|
||||
if (!$nodes->contains('id', $node->id) && !$node->disabled) {
|
||||
$nodes->add($node);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
return $nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description get product locations based on selected egg
|
||||
* @param Request $request
|
||||
* @param Egg $egg
|
||||
* @return Collection|JsonResponse
|
||||
*/
|
||||
public function getLocationsBasedOnEgg(Request $request, Egg $egg)
|
||||
{
|
||||
$nodes = $this->getNodesBasedOnEgg($request, $egg);
|
||||
$locations = collect();
|
||||
|
||||
//locations
|
||||
$nodes->each(function (Node $node) use ($nodes, $locations) {
|
||||
/** @var Location $location */
|
||||
$location = $node->location;
|
||||
|
||||
if (!$locations->contains('id', $location->id)) {
|
||||
$nodeIds = $nodes->map(function ($node) {
|
||||
return $node->id;
|
||||
});
|
||||
|
||||
$location->nodes = $location->nodes()
|
||||
->whereIn('id', $nodeIds)
|
||||
->get();
|
||||
|
||||
$locations->add($location);
|
||||
}
|
||||
});
|
||||
|
||||
return $locations;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Node $node
|
||||
* @param Egg $egg
|
||||
* @return Collection|JsonResponse
|
||||
*/
|
||||
public function getProductsBasedOnNode(Egg $egg, Node $node)
|
||||
{
|
||||
if (is_null($egg->id) || is_null($node->id)) return response()->json('node and egg id is required', '400');
|
||||
|
||||
return Product::query()
|
||||
->where('disabled', '=', false)
|
||||
->whereHas('nodes', function (Builder $builder) use ($node) {
|
||||
$builder->where('id', '=', $node->id);
|
||||
})
|
||||
->whereHas('eggs', function (Builder $builder) use ($egg) {
|
||||
$builder->where('id', '=', $egg->id);
|
||||
})
|
||||
->get();
|
||||
}
|
||||
}
|
|
@ -86,6 +86,6 @@ class ProfileController extends Controller
|
|||
'email' => $request->input('email'),
|
||||
]);
|
||||
|
||||
return redirect()->route('profile.index')->with('success' , 'profile updated');
|
||||
return redirect()->route('profile.index')->with('success' , 'Profile updated');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,11 +12,11 @@ use App\Models\Product;
|
|||
use App\Models\Server;
|
||||
use App\Notifications\ServerCreationError;
|
||||
use Exception;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\Client\Response;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Request as FacadesRequest;
|
||||
|
||||
class ServerController extends Controller
|
||||
|
@ -34,57 +34,36 @@ class ServerController extends Controller
|
|||
{
|
||||
if (!is_null($this->validateConfigurationRules())) return $this->validateConfigurationRules();
|
||||
|
||||
$productCount = Product::query()->where('disabled', '=', false)->count();
|
||||
$locations = Location::all();
|
||||
|
||||
$nodeCount = Node::query()
|
||||
->whereHas('products', function (Builder $builder) {
|
||||
$builder->where('disabled', '=', false);
|
||||
})->count();
|
||||
|
||||
$eggs = Egg::query()
|
||||
->whereHas('products', function (Builder $builder) {
|
||||
$builder->where('disabled', '=', false);
|
||||
})->get();
|
||||
|
||||
$nests = Nest::query()
|
||||
->whereHas('eggs', function (Builder $builder) {
|
||||
$builder->whereHas('products', function (Builder $builder) {
|
||||
$builder->where('disabled', '=', false);
|
||||
});
|
||||
})->get();
|
||||
|
||||
return view('servers.create')->with([
|
||||
'products' => Product::where('disabled', '=', false)->orderBy('price', 'asc')->get(),
|
||||
'locations' => Location::whereHas('nodes', function ($query) {
|
||||
$query->where('disabled', '=', false);
|
||||
})->get(),
|
||||
'nests' => Nest::where('disabled', '=', false)->get(),
|
||||
'minimum_credits' => Configuration::getValueByKey('MINIMUM_REQUIRED_CREDITS_TO_MAKE_SERVER', 50)
|
||||
'productCount' => $productCount,
|
||||
'nodeCount' => $nodeCount,
|
||||
'nests' => $nests,
|
||||
'locations' => $locations,
|
||||
'eggs' => $eggs,
|
||||
'user' => Auth::user(),
|
||||
]);
|
||||
}
|
||||
|
||||
/** Store a newly created resource in storage. */
|
||||
public function store(Request $request)
|
||||
{
|
||||
if (!is_null($this->validateConfigurationRules())) return $this->validateConfigurationRules();
|
||||
|
||||
$request->validate([
|
||||
"name" => "required|max:191",
|
||||
"description" => "nullable|max:191",
|
||||
"node_id" => "required|exists:nodes,id",
|
||||
"egg_id" => "required|exists:eggs,id",
|
||||
"product_id" => "required|exists:products,id"
|
||||
]);
|
||||
|
||||
//get required resources
|
||||
$egg = Egg::findOrFail($request->input('egg_id'));
|
||||
$node = Node::findOrFail($request->input('node_id'));
|
||||
$server = Auth::user()->servers()->create($request->all());
|
||||
|
||||
//get free allocation ID
|
||||
$allocationId = Pterodactyl::getFreeAllocationId($node);
|
||||
if (!$allocationId) return $this->noAllocationsError($server);
|
||||
|
||||
//create server on pterodactyl
|
||||
$response = Pterodactyl::createServer($server, $egg, $allocationId);
|
||||
if ($response->failed()) return $this->serverCreationFailed($response, $server);
|
||||
|
||||
//update server with pterodactyl_id
|
||||
$server->update([
|
||||
'pterodactyl_id' => $response->json()['attributes']['id'],
|
||||
'identifier' => $response->json()['attributes']['identifier']
|
||||
]);
|
||||
|
||||
if (Configuration::getValueByKey('SERVER_CREATE_CHARGE_FIRST_HOUR', 'true') == 'true') {
|
||||
if (Auth::user()->credits >= $server->product->getHourlyPrice()) {
|
||||
Auth::user()->decrement('credits', $server->product->getHourlyPrice());
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->route('servers.index')->with('success', 'server created');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|RedirectResponse
|
||||
*/
|
||||
|
@ -96,8 +75,8 @@ class ServerController extends Controller
|
|||
}
|
||||
|
||||
// minimum credits
|
||||
if (FacadesRequest::has("product_id")) {
|
||||
$product = Product::findOrFail(FacadesRequest::input("product_id"));
|
||||
if (FacadesRequest::has("product")) {
|
||||
$product = Product::findOrFail(FacadesRequest::input("product"));
|
||||
if (
|
||||
Auth::user()->credits <
|
||||
($product->minimum_credits == -1
|
||||
|
@ -121,17 +100,54 @@ class ServerController extends Controller
|
|||
return null;
|
||||
}
|
||||
|
||||
/** Remove the specified resource from storage. */
|
||||
public function destroy(Server $server)
|
||||
/** Store a newly created resource in storage. */
|
||||
public function store(Request $request)
|
||||
{
|
||||
try {
|
||||
$server->delete();
|
||||
return redirect()->route('servers.index')->with('success', 'server removed');
|
||||
} catch (Exception $e) {
|
||||
return redirect()->route('servers.index')->with('error', 'An exception has occurred while trying to remove a resource "' . $e->getMessage() . '"');
|
||||
}
|
||||
}
|
||||
/** @var Node $node */
|
||||
/** @var Egg $egg */
|
||||
/** @var Product $product */
|
||||
|
||||
if (!is_null($this->validateConfigurationRules())) return $this->validateConfigurationRules();
|
||||
|
||||
$request->validate([
|
||||
"name" => "required|max:191",
|
||||
"node" => "required|exists:nodes,id",
|
||||
"egg" => "required|exists:eggs,id",
|
||||
"product" => "required|exists:products,id"
|
||||
]);
|
||||
|
||||
//get required resources
|
||||
$product = Product::query()->findOrFail($request->input('product'));
|
||||
$egg = $product->eggs()->findOrFail($request->input('egg'));
|
||||
$node = $product->nodes()->findOrFail($request->input('node'));
|
||||
|
||||
$server = $request->user()->servers()->create([
|
||||
'name' => $request->input('name'),
|
||||
'product_id' => $request->input('product'),
|
||||
]);
|
||||
|
||||
//get free allocation ID
|
||||
$allocationId = Pterodactyl::getFreeAllocationId($node);
|
||||
if (!$allocationId) return $this->noAllocationsError($server);
|
||||
|
||||
//create server on pterodactyl
|
||||
$response = Pterodactyl::createServer($server, $egg, $allocationId);
|
||||
if ($response->failed()) return $this->serverCreationFailed($response, $server);
|
||||
|
||||
//update server with pterodactyl_id
|
||||
$server->update([
|
||||
'pterodactyl_id' => $response->json()['attributes']['id'],
|
||||
'identifier' => $response->json()['attributes']['identifier']
|
||||
]);
|
||||
|
||||
if (Configuration::getValueByKey('SERVER_CREATE_CHARGE_FIRST_HOUR', 'true') == 'true') {
|
||||
if ($request->user()->credits >= $server->product->getHourlyPrice()) {
|
||||
$request->user()->decrement('credits', $server->product->getHourlyPrice());
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->route('servers.index')->with('success', 'Server created');
|
||||
}
|
||||
|
||||
/**
|
||||
* return redirect with error
|
||||
|
@ -143,7 +159,7 @@ class ServerController extends Controller
|
|||
$server->delete();
|
||||
|
||||
Auth::user()->notify(new ServerCreationError($server));
|
||||
return redirect()->route('servers.index')->with('error', 'No allocations satisfying the requirements for automatic deployment were found.');
|
||||
return redirect()->route('servers.index')->with('error', 'No allocations satisfying the requirements for automatic deployment on this node were found.');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -158,4 +174,15 @@ class ServerController extends Controller
|
|||
|
||||
return redirect()->route('servers.index')->with('error', json_encode($response->json()));
|
||||
}
|
||||
|
||||
/** Remove the specified resource from storage. */
|
||||
public function destroy(Server $server)
|
||||
{
|
||||
try {
|
||||
$server->delete();
|
||||
return redirect()->route('servers.index')->with('success', 'Server removed');
|
||||
} catch (Exception $e) {
|
||||
return redirect()->route('servers.index')->with('error', 'An exception has occurred while trying to remove a resource "' . $e->getMessage() . '"');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,11 +4,14 @@ namespace App\Models;
|
|||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class Configuration extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public const CACHE_TAG = 'configuration';
|
||||
|
||||
public $primaryKey = 'key';
|
||||
|
||||
public $incrementing = false;
|
||||
|
@ -21,14 +24,25 @@ class Configuration extends Model
|
|||
'type',
|
||||
];
|
||||
|
||||
public static function boot()
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
static::updated(function (Configuration $configuration) {
|
||||
Cache::forget(self::CACHE_TAG .':'. $configuration->key);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param $default
|
||||
* @return mixed
|
||||
*/
|
||||
public static function getValueByKey(string $key , $default = null)
|
||||
public static function getValueByKey(string $key, $default = null)
|
||||
{
|
||||
$configuration = self::find($key);
|
||||
return $configuration ? $configuration->value : $default;
|
||||
return Cache::rememberForever(self::CACHE_TAG .':'. $key, function () use ($default, $key) {
|
||||
$configuration = self::find($key);
|
||||
return $configuration ? $configuration->value : $default;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ use App\Classes\Pterodactyl;
|
|||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
|
||||
class Egg extends Model
|
||||
{
|
||||
|
@ -21,14 +22,68 @@ class Egg extends Model
|
|||
'docker_image',
|
||||
'startup',
|
||||
'environment',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
/**
|
||||
* @return BelongsTo
|
||||
*/
|
||||
public function nest()
|
||||
public static function boot()
|
||||
{
|
||||
return $this->belongsTo(Nest::class, 'id', 'nest_id');
|
||||
parent::boot(); // TODO: Change the autogenerated stub
|
||||
|
||||
static::deleting(function (Egg $egg) {
|
||||
$egg->products()->detach();
|
||||
});
|
||||
}
|
||||
|
||||
public static function syncEggs()
|
||||
{
|
||||
Nest::syncNests();
|
||||
|
||||
Nest::all()->each(function (Nest $nest) {
|
||||
$eggs = Pterodactyl::getEggs($nest);
|
||||
|
||||
foreach ($eggs as $egg) {
|
||||
$array = [];
|
||||
$environment = [];
|
||||
|
||||
$array['id'] = $egg['attributes']['id'];
|
||||
$array['nest_id'] = $egg['attributes']['nest'];
|
||||
$array['name'] = $egg['attributes']['name'];
|
||||
$array['description'] = $egg['attributes']['description'];
|
||||
$array['docker_image'] = $egg['attributes']['docker_image'];
|
||||
$array['startup'] = $egg['attributes']['startup'];
|
||||
$array['updated_at'] = now();
|
||||
|
||||
//get environment variables
|
||||
foreach ($egg['attributes']['relationships']['variables']['data'] as $variable) {
|
||||
$environment[$variable['attributes']['env_variable']] = $variable['attributes']['default_value'];
|
||||
}
|
||||
|
||||
$array['environment'] = json_encode([$environment]);
|
||||
|
||||
self::query()->updateOrCreate([
|
||||
'id' => $array['id']
|
||||
], array_diff_key($array, array_flip(["id"]))
|
||||
);
|
||||
}
|
||||
|
||||
self::removeDeletedEggs($nest, $eggs);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description remove eggs that have been deleted on pterodactyl
|
||||
* @param Nest $nest
|
||||
* @param array $eggs
|
||||
*/
|
||||
private static function removeDeletedEggs(Nest $nest, array $eggs): void
|
||||
{
|
||||
$ids = array_map(function ($data) {
|
||||
return $data['attributes']['id'];
|
||||
}, $eggs);
|
||||
|
||||
$nest->eggs()->each(function (Egg $egg) use ($ids) {
|
||||
if (!in_array($egg->id, $ids)) $egg->delete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -39,7 +94,7 @@ class Egg extends Model
|
|||
$array = [];
|
||||
|
||||
foreach (json_decode($this->environment) as $variable) {
|
||||
foreach ($variable as $key => $value){
|
||||
foreach ($variable as $key => $value) {
|
||||
$array[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
@ -47,34 +102,19 @@ class Egg extends Model
|
|||
return $array;
|
||||
}
|
||||
|
||||
public static function syncEggs(){
|
||||
|
||||
Nest::all()->each(function (Nest $nest) {
|
||||
$eggs = Pterodactyl::getEggs($nest);
|
||||
|
||||
foreach ($eggs as $egg){
|
||||
$array = [];
|
||||
$environment = [];
|
||||
|
||||
$array['id'] = $egg['attributes']['id'];
|
||||
$array['nest_id'] = $egg['attributes']['nest'];
|
||||
$array['name'] = $egg['attributes']['name'];
|
||||
$array['description'] = $egg['attributes']['description'];
|
||||
$array['docker_image'] = $egg['attributes']['docker_image'];
|
||||
$array['startup'] = $egg['attributes']['startup'];
|
||||
|
||||
//get environment variables
|
||||
foreach ($egg['attributes']['relationships']['variables']['data'] as $variable){
|
||||
$environment[$variable['attributes']['env_variable']] = $variable['attributes']['default_value'];
|
||||
}
|
||||
|
||||
$array['environment'] = json_encode([$environment]);
|
||||
|
||||
self::firstOrCreate(['id' => $array['id']] , $array);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* @return BelongsTo
|
||||
*/
|
||||
public function nest()
|
||||
{
|
||||
return $this->belongsTo(Nest::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BelongsToMany
|
||||
*/
|
||||
public function products()
|
||||
{
|
||||
return $this->belongsToMany(Product::class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,28 +15,68 @@ class Location extends Model
|
|||
|
||||
public $guarded = [];
|
||||
|
||||
public static function boot()
|
||||
{
|
||||
parent::boot(); // TODO: Change the autogenerated stub
|
||||
|
||||
public function nodes(){
|
||||
return $this->hasMany(Node::class , 'location_id' , 'id');
|
||||
static::deleting(function (Location $location) {
|
||||
$location->nodes()->each(function (Node $node) {
|
||||
$node->delete();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync locations with pterodactyl panel
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function syncLocations(){
|
||||
public static function syncLocations()
|
||||
{
|
||||
$locations = Pterodactyl::getLocations();
|
||||
|
||||
$locations = array_map(function($val) {
|
||||
//map response
|
||||
$locations = array_map(function ($val) {
|
||||
return array(
|
||||
'id' => $val['attributes']['id'],
|
||||
'name' => $val['attributes']['short'],
|
||||
'id' => $val['attributes']['id'],
|
||||
'name' => $val['attributes']['short'],
|
||||
'description' => $val['attributes']['long']
|
||||
);
|
||||
}, $locations);
|
||||
|
||||
//update or create
|
||||
foreach ($locations as $location) {
|
||||
self::firstOrCreate(['id' => $location['id']] , $location);
|
||||
self::query()->updateOrCreate(
|
||||
[
|
||||
'id' => $location['id']
|
||||
],
|
||||
[
|
||||
'name' => $location['name'],
|
||||
'description' => $location['name'],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
self::removeDeletedLocation($locations);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description remove locations that have been deleted on pterodactyl
|
||||
* @param array $locations
|
||||
*/
|
||||
private static function removeDeletedLocation(array $locations): void
|
||||
{
|
||||
$ids = array_map(function ($data) {
|
||||
return $data['id'];
|
||||
}, $locations);
|
||||
|
||||
self::all()->each(function (Location $location) use ($ids) {
|
||||
if (!in_array($location->id, $ids)) $location->delete();
|
||||
});
|
||||
}
|
||||
|
||||
public function nodes()
|
||||
{
|
||||
return $this->hasMany(Node::class, 'location_id', 'id');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,17 +19,60 @@ class Nest extends Model
|
|||
'disabled',
|
||||
];
|
||||
|
||||
public static function boot()
|
||||
{
|
||||
parent::boot(); // TODO: Change the autogenerated stub
|
||||
|
||||
public function eggs(){
|
||||
return $this->hasMany(Egg::class);
|
||||
static::deleting(function (Nest $nest) {
|
||||
$nest->eggs()->each(function (Egg $egg) {
|
||||
$egg->delete();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public static function syncNests(){
|
||||
self::query()->delete();
|
||||
public static function syncNests()
|
||||
{
|
||||
$nests = Pterodactyl::getNests();
|
||||
|
||||
//map response
|
||||
$nests = array_map(function ($nest) {
|
||||
return array(
|
||||
'id' => $nest['attributes']['id'],
|
||||
'name' => $nest['attributes']['name'],
|
||||
'description' => $nest['attributes']['description'],
|
||||
);
|
||||
}, $nests);
|
||||
|
||||
foreach ($nests as $nest) {
|
||||
self::firstOrCreate(['id' => $nest['attributes']['id']] , array_merge($nest['attributes'] , ['disabled' => '1']));
|
||||
self::query()->updateOrCreate([
|
||||
'id' => $nest['id']
|
||||
], [
|
||||
'name' => $nest['name'],
|
||||
'description' => $nest['description'],
|
||||
'disabled' => false
|
||||
]);
|
||||
}
|
||||
|
||||
self::removeDeletedNests($nests);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description remove nests that have been deleted on pterodactyl
|
||||
* @param array $nests
|
||||
*/
|
||||
private static function removeDeletedNests(array $nests): void
|
||||
{
|
||||
$ids = array_map(function ($data) {
|
||||
return $data['id'];
|
||||
}, $nests);
|
||||
|
||||
self::all()->each(function (Nest $nest) use ($ids) {
|
||||
if (!in_array($nest->id, $ids)) $nest->delete();
|
||||
});
|
||||
}
|
||||
|
||||
public function eggs()
|
||||
{
|
||||
return $this->hasMany(Egg::class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ use Exception;
|
|||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
|
||||
class Node extends Model
|
||||
{
|
||||
|
@ -16,6 +17,65 @@ class Node extends Model
|
|||
|
||||
public $guarded = [];
|
||||
|
||||
public static function boot()
|
||||
{
|
||||
parent::boot(); // TODO: Change the autogenerated stub
|
||||
|
||||
static::deleting(function (Node $node) {
|
||||
$node->products()->detach();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function syncNodes()
|
||||
{
|
||||
Location::syncLocations();
|
||||
$nodes = Pterodactyl::getNodes();
|
||||
|
||||
//map response
|
||||
$nodes = array_map(function ($node) {
|
||||
return array(
|
||||
'id' => $node['attributes']['id'],
|
||||
'location_id' => $node['attributes']['location_id'],
|
||||
'name' => $node['attributes']['name'],
|
||||
'description' => $node['attributes']['description'],
|
||||
);
|
||||
}, $nodes);
|
||||
|
||||
//update or create
|
||||
foreach ($nodes as $node) {
|
||||
self::query()->updateOrCreate(
|
||||
[
|
||||
'id' => $node['id']
|
||||
],
|
||||
[
|
||||
'name' => $node['name'],
|
||||
'description' => $node['description'],
|
||||
'location_id' => $node['location_id'],
|
||||
'disabled' => false
|
||||
]);
|
||||
}
|
||||
|
||||
self::removeDeletedNodes($nodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description remove nodes that have been deleted on pterodactyl
|
||||
* @param array $nodes
|
||||
*/
|
||||
private static function removeDeletedNodes(array $nodes): void
|
||||
{
|
||||
$ids = array_map(function ($data) {
|
||||
return $data['id'];
|
||||
}, $nodes);
|
||||
|
||||
self::all()->each(function (Node $node) use ($ids) {
|
||||
if (!in_array($node->id, $ids)) $node->delete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BelongsTo
|
||||
|
@ -26,26 +86,10 @@ class Node extends Model
|
|||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
* @return BelongsToMany
|
||||
*/
|
||||
public static function syncNodes(){
|
||||
Location::syncLocations();
|
||||
$nodes = Pterodactyl::getNodes();
|
||||
|
||||
|
||||
$nodes = array_map(function($node) {
|
||||
return array(
|
||||
'id' => $node['attributes']['id'],
|
||||
'location_id' => $node['attributes']['location_id'],
|
||||
'name' => $node['attributes']['name'],
|
||||
'description' => $node['attributes']['description'],
|
||||
'disabled' => '1'
|
||||
);
|
||||
}, $nodes);
|
||||
|
||||
foreach ($nodes as $node) {
|
||||
self::firstOrCreate(['id' => $node['id']] , $node);
|
||||
}
|
||||
|
||||
public function products()
|
||||
{
|
||||
return $this->belongsToMany(Product::class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,9 @@ class Payment extends Model
|
|||
'type',
|
||||
'amount',
|
||||
'price',
|
||||
'tax_value',
|
||||
'total_price',
|
||||
'tax_percent',
|
||||
'currency_code',
|
||||
];
|
||||
|
||||
|
@ -51,9 +54,15 @@ class Payment extends Model
|
|||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function formatCurrency($locale = 'en_US')
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @param string $locale
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function formatToCurrency($value,$locale = 'en_US')
|
||||
{
|
||||
$formatter = new NumberFormatter($locale, NumberFormatter::CURRENCY);
|
||||
return $formatter->formatCurrency($this->price, $this->currency_code);
|
||||
return $formatter->formatCurrency($value, $this->currency_code);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ use Hidehalo\Nanoid\Client;
|
|||
use Illuminate\Database\Eloquent\Model;
|
||||
use NumberFormatter;
|
||||
use Spatie\Activitylog\Traits\LogsActivity;
|
||||
use App\Models\Configuration;
|
||||
|
||||
class PaypalProduct extends Model
|
||||
{
|
||||
|
@ -40,12 +41,45 @@ class PaypalProduct extends Model
|
|||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @param string $locale
|
||||
* @return string
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function formatCurrency($locale = 'en_US')
|
||||
public function formatToCurrency($value,$locale = 'en_US')
|
||||
{
|
||||
$formatter = new NumberFormatter($locale, NumberFormatter::CURRENCY);
|
||||
return $formatter->formatCurrency($this->price, $this->currency_code);
|
||||
return $formatter->formatCurrency($value, $this->currency_code);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns the tax in % taken from the Configuration
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getTaxPercent()
|
||||
{
|
||||
$tax = Configuration::getValueByKey("SALES_TAX");
|
||||
return $tax < 0 ? 0 : $tax;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns the tax as Number
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getTaxValue()
|
||||
{
|
||||
return $this->price*$this->getTaxPercent()/100;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns the full price of a Product including tax
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getTotalPrice()
|
||||
{
|
||||
return $this->price+($this->getTaxValue());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,11 +6,14 @@ use Hidehalo\Nanoid\Client;
|
|||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Spatie\Activitylog\Traits\LogsActivity;
|
||||
|
||||
class Product extends Model
|
||||
{
|
||||
use HasFactory, LogsActivity;
|
||||
use HasFactory;
|
||||
use LogsActivity;
|
||||
|
||||
public $incrementing = false;
|
||||
|
||||
|
@ -25,6 +28,11 @@ class Product extends Model
|
|||
|
||||
$product->{$product->getKeyName()} = $client->generateId($size = 21);
|
||||
});
|
||||
|
||||
static::deleting(function(Product $product) {
|
||||
$product->nodes()->detach();
|
||||
$product->eggs()->detach();
|
||||
});
|
||||
}
|
||||
|
||||
public function getHourlyPrice()
|
||||
|
@ -45,8 +53,22 @@ class Product extends Model
|
|||
/**
|
||||
* @return BelongsTo
|
||||
*/
|
||||
public function servers(): BelongsTo
|
||||
public function servers()
|
||||
{
|
||||
return $this->belongsTo(Server::class, 'id', 'product_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BelongsToMany
|
||||
*/
|
||||
public function eggs() {
|
||||
return $this->belongsToMany(Egg::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BelongsToMany
|
||||
*/
|
||||
public function nodes() {
|
||||
return $this->belongsToMany(Node::class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,9 +80,9 @@ class User extends Authenticatable implements MustVerifyEmail
|
|||
*/
|
||||
protected $casts = [
|
||||
'email_verified_at' => 'datetime',
|
||||
'last_seen' => 'datetime',
|
||||
'credits' => 'float',
|
||||
'server_limit' => 'float',
|
||||
'last_seen' => 'datetime',
|
||||
'credits' => 'float',
|
||||
'server_limit' => 'float',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
"yajra/laravel-datatables-oracle": "~9.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"barryvdh/laravel-debugbar": "^3.6",
|
||||
"facade/ignition": "^2.5",
|
||||
"fakerphp/faker": "^1.9.1",
|
||||
"laravel/sail": "^1.0.1",
|
||||
|
|
222
composer.lock
generated
222
composer.lock
generated
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "f7ba581ff6641d3ab79d558070e99f3c",
|
||||
"content-hash": "500346cc4a4a83b162e07bb0071d1602",
|
||||
"packages": [
|
||||
{
|
||||
"name": "asm89/stack-cors",
|
||||
|
@ -6202,6 +6202,91 @@
|
|||
}
|
||||
],
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "barryvdh/laravel-debugbar",
|
||||
"version": "v3.6.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/barryvdh/laravel-debugbar.git",
|
||||
"reference": "3c2d678269ba60e178bcd93e36f6a91c36b727f1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/3c2d678269ba60e178bcd93e36f6a91c36b727f1",
|
||||
"reference": "3c2d678269ba60e178bcd93e36f6a91c36b727f1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"illuminate/routing": "^6|^7|^8",
|
||||
"illuminate/session": "^6|^7|^8",
|
||||
"illuminate/support": "^6|^7|^8",
|
||||
"maximebf/debugbar": "^1.17.2",
|
||||
"php": ">=7.2",
|
||||
"symfony/debug": "^4.3|^5",
|
||||
"symfony/finder": "^4.3|^5"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "^1.3.3",
|
||||
"orchestra/testbench-dusk": "^4|^5|^6",
|
||||
"phpunit/phpunit": "^8.5|^9.0",
|
||||
"squizlabs/php_codesniffer": "^3.5"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.6-dev"
|
||||
},
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Barryvdh\\Debugbar\\ServiceProvider"
|
||||
],
|
||||
"aliases": {
|
||||
"Debugbar": "Barryvdh\\Debugbar\\Facade"
|
||||
}
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Barryvdh\\Debugbar\\": "src/"
|
||||
},
|
||||
"files": [
|
||||
"src/helpers.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Barry vd. Heuvel",
|
||||
"email": "barryvdh@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "PHP Debugbar integration for Laravel",
|
||||
"keywords": [
|
||||
"debug",
|
||||
"debugbar",
|
||||
"laravel",
|
||||
"profiler",
|
||||
"webprofiler"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/barryvdh/laravel-debugbar/issues",
|
||||
"source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.6.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://fruitcake.nl",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/barryvdh",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2021-10-21T10:57:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/instantiator",
|
||||
"version": "1.4.0",
|
||||
|
@ -6713,6 +6798,71 @@
|
|||
},
|
||||
"time": "2021-05-25T16:41:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "maximebf/debugbar",
|
||||
"version": "v1.17.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/maximebf/php-debugbar.git",
|
||||
"reference": "e8ac3499af0ea5b440908e06cc0abe5898008b3c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/e8ac3499af0ea5b440908e06cc0abe5898008b3c",
|
||||
"reference": "e8ac3499af0ea5b440908e06cc0abe5898008b3c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.1|^8",
|
||||
"psr/log": "^1|^2|^3",
|
||||
"symfony/var-dumper": "^2.6|^3|^4|^5"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^7.5.20 || ^9.4.2"
|
||||
},
|
||||
"suggest": {
|
||||
"kriswallsmith/assetic": "The best way to manage assets",
|
||||
"monolog/monolog": "Log using Monolog",
|
||||
"predis/predis": "Redis storage"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.17-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"DebugBar\\": "src/DebugBar/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Maxime Bouroumeau-Fuseau",
|
||||
"email": "maxime.bouroumeau@gmail.com",
|
||||
"homepage": "http://maximebf.com"
|
||||
},
|
||||
{
|
||||
"name": "Barry vd. Heuvel",
|
||||
"email": "barryvdh@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "Debug bar in the browser for php application",
|
||||
"homepage": "https://github.com/maximebf/php-debugbar",
|
||||
"keywords": [
|
||||
"debug",
|
||||
"debugbar"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/maximebf/php-debugbar/issues",
|
||||
"source": "https://github.com/maximebf/php-debugbar/tree/v1.17.3"
|
||||
},
|
||||
"time": "2021-10-19T12:33:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mockery/mockery",
|
||||
"version": "1.4.3",
|
||||
|
@ -8652,6 +8802,74 @@
|
|||
],
|
||||
"time": "2020-09-28T06:39:44+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/debug",
|
||||
"version": "v4.4.31",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/debug.git",
|
||||
"reference": "43ede438d4cb52cd589ae5dc070e9323866ba8e0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/debug/zipball/43ede438d4cb52cd589ae5dc070e9323866ba8e0",
|
||||
"reference": "43ede438d4cb52cd589ae5dc070e9323866ba8e0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1.3",
|
||||
"psr/log": "^1|^2|^3"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/http-kernel": "<3.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/http-kernel": "^3.4|^4.0|^5.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\Debug\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Provides tools to ease debugging PHP code",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/debug/tree/v4.4.31"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-09-24T13:30:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "theseer/tokenizer",
|
||||
"version": "1.2.0",
|
||||
|
@ -8713,5 +8931,5 @@
|
|||
"ext-intl": "*"
|
||||
},
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.1.0"
|
||||
"plugin-api-version": "2.0.0"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateEggProductTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('egg_product', function (Blueprint $table) {
|
||||
$table->foreignId('egg_id')->constrained();
|
||||
$table->foreignUuid('product_id')->constrained();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('egg_product');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateNodeProductTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('node_product', function (Blueprint $table) {
|
||||
$table->foreignId('node_id')->constrained();
|
||||
$table->foreignUuid('product_id')->constrained();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('node_product');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddDisabledToEggsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('eggs', function (Blueprint $table) {
|
||||
$table->boolean('disabled')->default(false);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('eggs', function (Blueprint $table) {
|
||||
$table->dropColumn('disabled');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddTaxToPaymentlogs extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('payments', function (Blueprint $table) {
|
||||
$table->decimal('tax_value',8,2)->after('price')->nullable();
|
||||
$table->integer('tax_percent')->after('tax_value')->nullable();
|
||||
$table->decimal('total_price',8,2)->after('tax_percent')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('payments', function (Blueprint $table) {
|
||||
$table->dropColumn('tax_value');
|
||||
$table->dropColumn('tax_percent');
|
||||
$table->dropColumn('total_price');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -136,6 +136,14 @@ class ConfigurationSeeder extends Seeder
|
|||
'type' => 'boolean',
|
||||
'description' => 'Charges the first hour worth of credits upon creating a server.'
|
||||
]);
|
||||
//sales tax
|
||||
Configuration::firstOrCreate([
|
||||
'key' => 'SALES_TAX',
|
||||
], [
|
||||
'value' => '0',
|
||||
'type' => 'integer',
|
||||
'description' => 'The %-value of tax that will be added to the product price on checkout'
|
||||
]);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
13843
package-lock.json
generated
13843
package-lock.json
generated
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
2
public/js/app.js
vendored
2
public/js/app.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
5
resources/js/alpine.js
vendored
Normal file
5
resources/js/alpine.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
resources/js/app.js
vendored
1
resources/js/app.js
vendored
|
@ -3,3 +3,4 @@ require('./slim.kickstart.min')
|
|||
require('./bootstrap');
|
||||
|
||||
|
||||
|
||||
|
|
8
resources/js/bootstrap.js
vendored
8
resources/js/bootstrap.js
vendored
|
@ -18,10 +18,10 @@ try {
|
|||
* to our Laravel back-end. This library automatically handles sending the
|
||||
* CSRF token as a header based on the value of the "XSRF" token cookie.
|
||||
*/
|
||||
//
|
||||
// window.axios = require('axios');
|
||||
//
|
||||
// window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||
|
||||
window.axios = require('axios');
|
||||
|
||||
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||
|
||||
/**
|
||||
* Echo exposes an expressive API for subscribing to channels and listening
|
||||
|
|
153
resources/views/admin/overview/index.blade.php
Normal file
153
resources/views/admin/overview/index.blade.php
Normal file
|
@ -0,0 +1,153 @@
|
|||
@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>Admin Overview</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 class="text-muted"
|
||||
href="{{route('admin.overview.index')}}">Admin Overview</a></li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- END CONTENT HEADER -->
|
||||
|
||||
<!-- MAIN CONTENT -->
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-3">
|
||||
<a href="https://discord.gg/4Y6HjD2uyU" class="btn btn-dark btn-block px-3"><i
|
||||
class="fab fa-discord mr-2"></i> {{__('Support server')}}</a>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<a href="https://controlpanel.gg/docs/intro" class="btn btn-dark btn-block px-3"><i
|
||||
class="fas fa-link mr-2"></i> {{__('Documentation')}}</a>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<a href="https://github.com/ControlPanel-gg/dashboard" class="btn btn-dark btn-block px-3"><i
|
||||
class="fab fa-github mr-2"></i> {{__('Github')}}</a>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<a href="https://controlpanel.gg/docs/Contributing/donating" class="btn btn-dark btn-block px-3"><i
|
||||
class="fas fa-money-bill mr-2"></i> {{__('Support ControlPanel')}}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-6 col-md-3">
|
||||
<div class="info-box">
|
||||
<span class="info-box-icon bg-info elevation-1"><i class="fas fa-server"></i></span>
|
||||
|
||||
<div class="info-box-content">
|
||||
<span class="info-box-text">{{__('Servers')}}</span>
|
||||
<span class="info-box-number">{{$serverCount}}</span>
|
||||
</div>
|
||||
<!-- /.info-box-content -->
|
||||
</div>
|
||||
<!-- /.info-box -->
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-sm-6 col-md-3">
|
||||
<div class="info-box">
|
||||
<span class="info-box-icon bg-primary elevation-1"><i class="fas fa-users"></i></span>
|
||||
|
||||
<div class="info-box-content">
|
||||
<span class="info-box-text">{{__('Users')}}</span>
|
||||
<span class="info-box-number">{{$userCount}}</span>
|
||||
</div>
|
||||
<!-- /.info-box-content -->
|
||||
</div>
|
||||
<!-- /.info-box -->
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-sm-6 col-md-3">
|
||||
<div class="info-box">
|
||||
<span class="info-box-icon bg-warning elevation-1"><i
|
||||
class="fas fa-coins text-white"></i></span>
|
||||
|
||||
<div class="info-box-content">
|
||||
<span class="info-box-text">{{__('Total')}} {{CREDITS_DISPLAY_NAME}}</span>
|
||||
<span class="info-box-number">{{$creditCount}}</span>
|
||||
</div>
|
||||
<!-- /.info-box-content -->
|
||||
</div>
|
||||
<!-- /.info-box -->
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-sm-6 col-md-3">
|
||||
<div class="info-box">
|
||||
<span class="info-box-icon bg-success elevation-1"><i class="fas fa-money-bill"></i></span>
|
||||
|
||||
<div class="info-box-content">
|
||||
<span class="info-box-text">{{__('Payments')}}</span>
|
||||
<span class="info-box-number">{{$paymentCount}}</span>
|
||||
</div>
|
||||
<!-- /.info-box-content -->
|
||||
</div>
|
||||
<!-- /.info-box -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div class="card-title ">
|
||||
<span><i class="fas fa-kiwi-bird mr-2"></i>{{__('Pterodactyl')}}</span>
|
||||
</div>
|
||||
<a href="{{route('admin.overview.sync')}}" class="btn btn-primary btn-sm"><i
|
||||
class="fas fa-sync mr-2"></i>{{__('Sync')}}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body py-1">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{__('Resources')}}</th>
|
||||
<th>{{__('Count')}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{__('Locations')}}</td>
|
||||
<td>{{$locationCount}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{__('Nodes')}}</td>
|
||||
<td>{{$nodeCount}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{__('Nests')}}</td>
|
||||
<td>{{$nestCount}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{__('Eggs')}}</td>
|
||||
<td>{{$eggCount}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<span><i class="fas fa-sync mr-2"></i>{{__('Last updated :date', ['date' => $syncLastUpdate])}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- END CUSTOM CONTENT -->
|
||||
|
||||
</section>
|
||||
<!-- END CONTENT -->
|
||||
@endsection
|
|
@ -37,7 +37,10 @@
|
|||
<th>User</th>
|
||||
<th>Type</th>
|
||||
<th>Amount</th>
|
||||
<th>Price</th>
|
||||
<th>Product Price</th>
|
||||
<th>Tax</th>
|
||||
<th>Tax(%)</th>
|
||||
<th>Total Price</th>
|
||||
<th>Payment_ID</th>
|
||||
<th>Payer_ID</th>
|
||||
<th>Created at</th>
|
||||
|
@ -68,6 +71,9 @@
|
|||
{data: 'type'},
|
||||
{data: 'amount'},
|
||||
{data: 'price'},
|
||||
{data: 'tax_value'},
|
||||
{data: 'tax_percent'},
|
||||
{data: 'total_price'},
|
||||
{data: 'payment_id'},
|
||||
{data: 'payer_id'},
|
||||
{data: 'created_at'},
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<li class="breadcrumb-item"><a href="{{ route('home') }}">Dashboard</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{ route('admin.products.index') }}">Products</a></li>
|
||||
<li class="breadcrumb-item"><a class="text-muted"
|
||||
href="{{ route('admin.products.create') }}">Create</a>
|
||||
href="{{ route('admin.products.create') }}">Create</a>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
@ -25,19 +25,22 @@
|
|||
<!-- MAIN CONTENT -->
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
<form action="{{route('admin.products.store')}}" method="POST">
|
||||
@csrf
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title">Product Details</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form action="{{ route('admin.products.store') }}" method="POST">
|
||||
@csrf
|
||||
<div class="d-flex flex-row-reverse">
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" name="disabled"
|
||||
class="custom-control-input custom-control-input-danger" id="switch1">
|
||||
<label class="custom-control-label" for="switch1">Disabled <i data-toggle="popover"
|
||||
data-trigger="hover"
|
||||
class="custom-control-input custom-control-input-danger" id="switch1">
|
||||
<label class="custom-control-label" for="switch1">Disabled <i
|
||||
data-toggle="popover" data-trigger="hover"
|
||||
data-content="Will hide this option from being selected"
|
||||
class="fas fa-info-circle"></i></label>
|
||||
</div>
|
||||
|
@ -47,75 +50,84 @@
|
|||
<div class="col-lg-6">
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<input value="{{ old('name') }}" id="name" name="name" type="text"
|
||||
class="form-control @error('name') is-invalid @enderror"
|
||||
required="required">
|
||||
<input value="{{$product->name ?? old('name')}}" id="name" name="name"
|
||||
type="text"
|
||||
class="form-control @error('name') is-invalid @enderror"
|
||||
required="required">
|
||||
@error('name')
|
||||
<div class="invalid-feedback">
|
||||
{{ $message }}
|
||||
</div>
|
||||
<div class="invalid-feedback">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="price">Price in {{ CREDITS_DISPLAY_NAME }}</label>
|
||||
<input value="{{ old('price') }}" id="price" name="price" type="number"
|
||||
class="form-control @error('price') is-invalid @enderror"
|
||||
required="required">
|
||||
<label for="price">Price in credits</label>
|
||||
<input value="{{$product->price ?? old('price')}}" id="price" name="price"
|
||||
type="number"
|
||||
class="form-control @error('price') is-invalid @enderror"
|
||||
required="required">
|
||||
@error('price')
|
||||
<div class="invalid-feedback">
|
||||
{{ $message }}
|
||||
</div>
|
||||
<div class="invalid-feedback">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="memory">Memory</label>
|
||||
<input value="{{ old('memory') }}" id="memory" name="memory" type="number"
|
||||
class="form-control @error('memory') is-invalid @enderror"
|
||||
required="required">
|
||||
<input value="{{$product->memory ?? old('memory')}}" id="memory"
|
||||
name="memory"
|
||||
type="number"
|
||||
class="form-control @error('memory') is-invalid @enderror"
|
||||
required="required">
|
||||
@error('memory')
|
||||
<div class="invalid-feedback">
|
||||
{{ $message }}
|
||||
</div>
|
||||
<div class="invalid-feedback">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="cpu">Cpu</label>
|
||||
<input value="{{ old('cpu') }}" id="cpu" name="cpu" type="number"
|
||||
class="form-control @error('cpu') is-invalid @enderror" required="required">
|
||||
<input value="{{$product->cpu ?? old('cpu')}}" id="cpu" name="cpu"
|
||||
type="number"
|
||||
class="form-control @error('cpu') is-invalid @enderror"
|
||||
required="required">
|
||||
@error('cpu')
|
||||
<div class="invalid-feedback">
|
||||
{{ $message }}
|
||||
</div>
|
||||
<div class="invalid-feedback">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="swap">Swap</label>
|
||||
<input value="{{ old('swap') }}" id="swap" name="swap" type="number"
|
||||
class="form-control @error('swap') is-invalid @enderror"
|
||||
required="required">
|
||||
<input value="{{$product->swap ?? old('swap')}}" id="swap" name="swap"
|
||||
type="number"
|
||||
class="form-control @error('swap') is-invalid @enderror"
|
||||
required="required">
|
||||
@error('swap')
|
||||
<div class="invalid-feedback">
|
||||
{{ $message }}
|
||||
</div>
|
||||
<div class="invalid-feedback">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="description">Description <i data-toggle="popover"
|
||||
data-trigger="hover" data-content="This is what the users sees"
|
||||
class="fas fa-info-circle"></i></label>
|
||||
<textarea id="description" name="description" type="text"
|
||||
class="form-control @error('description') is-invalid @enderror"
|
||||
required="required">{{ old('description') }}</textarea>
|
||||
data-trigger="hover"
|
||||
data-content="This is what the users sees"
|
||||
class="fas fa-info-circle"></i></label>
|
||||
<textarea id="description" name="description"
|
||||
type="text"
|
||||
class="form-control @error('description') is-invalid @enderror"
|
||||
required="required">{{$product->description ?? old('description')}}</textarea>
|
||||
@error('description')
|
||||
<div class="invalid-feedback">
|
||||
{{ $message }}
|
||||
</div>
|
||||
<div class="invalid-feedback">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
|
@ -123,13 +135,15 @@
|
|||
<div class="col-lg-6">
|
||||
<div class="form-group">
|
||||
<label for="disk">Disk</label>
|
||||
<input value="{{ old('disk') ?? 1000 }}" id="disk" name="disk" type="number"
|
||||
class="form-control @error('disk') is-invalid @enderror"
|
||||
required="required">
|
||||
<input value="{{$product->disk ?? old('disk') ?? 1000}}" id="disk"
|
||||
name="disk"
|
||||
type="number"
|
||||
class="form-control @error('disk') is-invalid @enderror"
|
||||
required="required">
|
||||
@error('disk')
|
||||
<div class="invalid-feedback">
|
||||
{{ $message }}
|
||||
</div>
|
||||
<div class="invalid-feedback">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
|
@ -138,59 +152,67 @@
|
|||
data-toggle="popover" data-trigger="hover"
|
||||
data-content="Setting to -1 will use the value from configuration."
|
||||
class="fas fa-info-circle"></i></label>
|
||||
<input value="{{ old('minimum_credits') ?? -1 }}" id="minimum_credits"
|
||||
name="minimum_credits" type="number"
|
||||
class="form-control @error('minimum_credits') is-invalid @enderror"
|
||||
required="required">
|
||||
<input value="{{ $product->minimum_credits ?? old('minimum_credits') ?? -1 }}" id="minimum_credits"
|
||||
name="minimum_credits" type="number"
|
||||
class="form-control @error('minimum_credits') is-invalid @enderror"
|
||||
required="required">
|
||||
@error('minimum_credits')
|
||||
<div class="invalid-feedback">
|
||||
{{ $message }}
|
||||
</div>
|
||||
<div class="invalid-feedback">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="io">IO</label>
|
||||
<input value="{{ old('io') ?? 500 }}" id="io" name="io" type="number"
|
||||
class="form-control @error('io') is-invalid @enderror" required="required">
|
||||
<input value="{{$product->io ?? old('io') ?? 500}}" id="io" name="io"
|
||||
type="number"
|
||||
class="form-control @error('io') is-invalid @enderror"
|
||||
required="required">
|
||||
@error('io')
|
||||
<div class="invalid-feedback">
|
||||
{{ $message }}
|
||||
</div>
|
||||
<div class="invalid-feedback">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="databases">Databases</label>
|
||||
<input value="{{ old('databases') ?? 1 }}" id="databases" name="databases"
|
||||
type="number" class="form-control @error('databases') is-invalid @enderror"
|
||||
required="required">
|
||||
<input value="{{$product->databases ?? old('databases') ?? 1}}"
|
||||
id="databases"
|
||||
name="databases"
|
||||
type="number"
|
||||
class="form-control @error('databases') is-invalid @enderror"
|
||||
required="required">
|
||||
@error('databases')
|
||||
<div class="invalid-feedback">
|
||||
{{ $message }}
|
||||
</div>
|
||||
<div class="invalid-feedback">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="backups">Backups</label>
|
||||
<input value="{{ old('backups') ?? 1 }}" id="backups" name="backups"
|
||||
type="number" class="form-control @error('backups') is-invalid @enderror"
|
||||
required="required">
|
||||
<input value="{{$product->backups ?? old('backups') ?? 1}}" id="backups"
|
||||
name="backups"
|
||||
type="number"
|
||||
class="form-control @error('backups') is-invalid @enderror"
|
||||
required="required">
|
||||
@error('backups')
|
||||
<div class="invalid-feedback">
|
||||
{{ $message }}
|
||||
</div>
|
||||
<div class="invalid-feedback">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="allocations">Allocations</label>
|
||||
<input value="{{ old('allocations') ?? 0 }}" id="allocations"
|
||||
name="allocations" type="number"
|
||||
class="form-control @error('allocations') is-invalid @enderror"
|
||||
required="required">
|
||||
<input value="{{$product->allocations ?? old('allocations') ?? 0}}"
|
||||
id="allocations" name="allocations"
|
||||
type="number"
|
||||
class="form-control @error('allocations') is-invalid @enderror"
|
||||
required="required">
|
||||
@error('allocations')
|
||||
<div class="invalid-feedback">
|
||||
{{ $message }}
|
||||
</div>
|
||||
<div class="invalid-feedback">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
@ -201,21 +223,88 @@
|
|||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title">Product Linking
|
||||
<i data-toggle="popover"
|
||||
data-trigger="hover"
|
||||
data-content="Link your products to nodes and eggs to create dynamic pricing for each option"
|
||||
class="fas fa-info-circle"></i></h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="nodes">Nodes</label>
|
||||
<select id="nodes" style="width:100%"
|
||||
class="custom-select @error('nodes') is-invalid @enderror"
|
||||
name="nodes[]" multiple="multiple" autocomplete="off">
|
||||
@foreach($locations as $location)
|
||||
<optgroup label="{{$location->name}}">
|
||||
@foreach($location->nodes as $node)
|
||||
<option
|
||||
@if(isset($product)) @if($product->nodes->contains('id' , $node->id)) selected
|
||||
@endif @endif value="{{$node->id}}">{{$node->name}}</option>
|
||||
@endforeach
|
||||
</optgroup>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('nodes')
|
||||
<div class="text-danger">
|
||||
{{$message}}
|
||||
</div>
|
||||
@enderror
|
||||
<div class="text-muted">
|
||||
This product will only be available for these nodes
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="eggs">Eggs</label>
|
||||
<select id="eggs" style="width:100%"
|
||||
class="custom-select @error('eggs') is-invalid @enderror"
|
||||
name="eggs[]" multiple="multiple" autocomplete="off">
|
||||
@foreach($nests as $nest)
|
||||
<optgroup label="{{$nest->name}}">
|
||||
@foreach($nest->eggs as $egg)
|
||||
<option
|
||||
@if(isset($product)) @if($product->eggs->contains('id' , $egg->id)) selected
|
||||
@endif @endif value="{{$egg->id}}">{{$egg->name}}</option>
|
||||
@endforeach
|
||||
</optgroup>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('eggs')
|
||||
<div class="text-danger">
|
||||
{{$message}}
|
||||
</div>
|
||||
@enderror
|
||||
<div class="text-muted">
|
||||
This product will only be available for these eggs
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
<!-- END CONTENT -->
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
$('[data-toggle="popover"]').popover();
|
||||
$('.custom-select').select2();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@endsection
|
||||
|
|
|
@ -10,10 +10,10 @@
|
|||
</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('admin.products.index') }}">Products</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{route('home')}}">Dashboard</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{route('admin.products.index')}}">Products</a></li>
|
||||
<li class="breadcrumb-item"><a class="text-muted"
|
||||
href="{{ route('admin.products.edit', $product->id) }}">Edit</a>
|
||||
href="{{route('admin.products.edit' , $product->id)}}">Edit</a>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
@ -25,31 +25,34 @@
|
|||
<!-- MAIN CONTENT -->
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
<form action="{{route('admin.products.update' , $product->id)}}" method="POST">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
@if($product->servers()->count() > 0)
|
||||
<div class="callout callout-danger">
|
||||
<h4>Editing the resource options will not automatically update the servers on
|
||||
pterodactyl's side!</h4>
|
||||
<p class="text-muted">Automatically updating resource options on pterodactyl side is on
|
||||
my todo list :)</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($product->servers()->count() > 0)
|
||||
<div class="callout callout-danger">
|
||||
<h4>Editing the resource options will not automatically update the servers on pterodactyl's
|
||||
side!</h4>
|
||||
<p class="text-muted">Automatically updating resource options on pterodactyl side is on my
|
||||
todo list :)</p>
|
||||
</div>
|
||||
@endif
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title">Product Details</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form action="{{ route('admin.products.update', $product->id) }}" method="POST">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
<div class="d-flex flex-row-reverse">
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" @if ($product->disabled) checked @endif name="disabled"
|
||||
class="custom-control-input custom-control-input-danger" id="switch1">
|
||||
<label class="custom-control-label" for="switch1">Disabled <i data-toggle="popover"
|
||||
data-trigger="hover"
|
||||
<input type="checkbox" @if($product->disabled) checked @endif name="disabled"
|
||||
class="custom-control-input custom-control-input-danger" id="switch1">
|
||||
<label class="custom-control-label" for="switch1">Disabled <i
|
||||
data-toggle="popover" data-trigger="hover"
|
||||
data-content="Will hide this option from being selected"
|
||||
class="fas fa-info-circle"></i></label>
|
||||
</div>
|
||||
|
@ -57,6 +60,7 @@
|
|||
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<input value="{{ $product->name }}" id="name" name="name" type="text"
|
||||
|
@ -118,11 +122,13 @@
|
|||
|
||||
<div class="form-group">
|
||||
<label for="description">Description <i data-toggle="popover"
|
||||
data-trigger="hover" data-content="This is what the users sees"
|
||||
class="fas fa-info-circle"></i></label>
|
||||
<textarea id="description" name="description" type="text"
|
||||
class="form-control @error('description') is-invalid @enderror"
|
||||
required="required">{{ $product->description }}</textarea>
|
||||
data-trigger="hover"
|
||||
data-content="This is what the users sees"
|
||||
class="fas fa-info-circle"></i></label>
|
||||
<textarea id="description" name="description"
|
||||
type="text"
|
||||
class="form-control @error('description') is-invalid @enderror"
|
||||
required="required">{{$product->description}}</textarea>
|
||||
@error('description')
|
||||
<div class="invalid-feedback">
|
||||
{{ $message }}
|
||||
|
@ -210,21 +216,89 @@
|
|||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title">Product Linking
|
||||
<i data-toggle="popover"
|
||||
data-trigger="hover"
|
||||
data-content="Link your products to nodes and eggs to create dynamic pricing for each option"
|
||||
class="fas fa-info-circle"></i></h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="nodes">Nodes</label>
|
||||
<select id="nodes" style="width:100%"
|
||||
class="custom-select @error('nodes') is-invalid @enderror" name="nodes[]"
|
||||
multiple="multiple" autocomplete="off">
|
||||
@foreach($locations as $location)
|
||||
<optgroup label="{{$location->name}}">
|
||||
@foreach($location->nodes as $node)
|
||||
<option @if($product->nodes->contains('id' , $node->id)) selected
|
||||
@endif value="{{$node->id}}">{{$node->name}}</option>
|
||||
@endforeach
|
||||
</optgroup>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('nodes')
|
||||
<div class="text-danger">
|
||||
{{$message}}
|
||||
</div>
|
||||
@enderror
|
||||
<div class="text-muted">
|
||||
This product will only be available for these nodes
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="eggs">Eggs</label>
|
||||
<select id="eggs" style="width:100%"
|
||||
class="custom-select @error('eggs') is-invalid @enderror" name="eggs[]"
|
||||
multiple="multiple" autocomplete="off">
|
||||
@foreach($nests as $nest)
|
||||
<optgroup label="{{$nest->name}}">
|
||||
@foreach($nest->eggs as $egg)
|
||||
<option @if($product->eggs->contains('id' , $egg->id)) selected
|
||||
@endif value="{{$egg->id}}">{{$egg->name}}</option>
|
||||
@endforeach
|
||||
</optgroup>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('eggs')
|
||||
<div class="text-danger">
|
||||
{{$message}}
|
||||
</div>
|
||||
@enderror
|
||||
<div class="text-muted">
|
||||
This product will only be available for these eggs
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
<!-- END CONTENT -->
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', (event) => {
|
||||
$('.custom-select').select2();
|
||||
})
|
||||
</script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
$('[data-toggle="popover"]').popover();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@endsection
|
||||
|
|
|
@ -46,10 +46,10 @@
|
|||
<th>Cpu</th>
|
||||
<th>Swap</th>
|
||||
<th>Disk</th>
|
||||
<th>IO</th>
|
||||
<th>Databases</th>
|
||||
<th>Backups</th>
|
||||
<th>Allocations</th>
|
||||
<th>Eggs</th>
|
||||
<th>Nodes</th>
|
||||
<th>Servers</th>
|
||||
<th>Created at</th>
|
||||
<th></th>
|
||||
|
@ -79,6 +79,7 @@
|
|||
processing: true,
|
||||
serverSide: true,
|
||||
stateSave: true,
|
||||
order: [[ 2, "asc" ]],
|
||||
ajax: "{{route('admin.products.datatable')}}",
|
||||
columns: [
|
||||
{data: 'disabled'},
|
||||
|
@ -88,10 +89,10 @@
|
|||
{data: 'cpu'},
|
||||
{data: 'swap'},
|
||||
{data: 'disk'},
|
||||
{data: 'io'},
|
||||
{data: 'databases'},
|
||||
{data: 'backups'},
|
||||
{data: 'allocations'},
|
||||
{data: 'nodes', sortable: false},
|
||||
{data: 'eggs', sortable: false},
|
||||
{data: 'servers', sortable: false},
|
||||
{data: 'created_at'},
|
||||
{data: 'actions', sortable: false},
|
||||
|
|
|
@ -45,41 +45,38 @@
|
|||
<div class="tab-content">
|
||||
<div class="tab-pane mt-3 active" id="dashboard-icons">
|
||||
|
||||
<form method="POST" class="mb-3" action="{{route('admin.settings.update.icons')}}">
|
||||
<form method="POST" enctype="multipart/form-data" class="mb-3"
|
||||
action="{{route('admin.settings.update.icons')}}">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
|
||||
<div class="d-flex">
|
||||
<div class="form-group">
|
||||
<div class="text-center mb-2">Dashboard Icon</div>
|
||||
<div class="avatar">
|
||||
<div class="slim rounded-circle"
|
||||
data-size="128,128"
|
||||
data-ratio="1:1"
|
||||
style="width: 140px;height:140px; cursor: pointer"
|
||||
data-save-initial-image="true">
|
||||
<input type="file" name="icon"/>
|
||||
<img
|
||||
src="{{\Illuminate\Support\Facades\Storage::disk('public')->exists('icon.png') ? asset('storage/icon.png') : asset('images/bitsec.png')}}"
|
||||
alt="icon">
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-lg-4 col-12">
|
||||
<div class="form-group">
|
||||
<div class="custom-file mb-3 mt-3">
|
||||
<input type="file" accept="image/png,image/jpeg,image/jpg"
|
||||
class="custom-file-input" name="icon" id="icon">
|
||||
<label class="custom-file-label selected"
|
||||
for="icon">{{__('Select panel icon')}}</label>
|
||||
</div>
|
||||
@error('icon')
|
||||
<span class="text-danger">
|
||||
{{$message}}
|
||||
</span>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group ml-5">
|
||||
<div class="text-center mb-2">Favicon</div>
|
||||
<div class="avatar">
|
||||
<div class="slim rounded-circle"
|
||||
data-size="64,64"
|
||||
data-ratio="1:1"
|
||||
style="width: 140px;height:140px; cursor: pointer"
|
||||
data-save-initial-image="true">
|
||||
<input accept="image/x-icon" type="file" name="favicon"/>
|
||||
<img
|
||||
src="{{\Illuminate\Support\Facades\Storage::disk('public')->exists('favicon.ico') ? asset('storage/favicon.ico') : asset('favicon.ico')}}"
|
||||
alt="favicon">
|
||||
<div class="form-group">
|
||||
<div class="custom-file mb-3">
|
||||
<input type="file" accept="image/x-icon" class="custom-file-input"
|
||||
name="favicon" id="favicon">
|
||||
<label class="custom-file-label selected"
|
||||
for="favicon">{{__('Select panel favicon')}}</label>
|
||||
</div>
|
||||
@error('favicon')
|
||||
<span class="text-danger">
|
||||
{{$message}}
|
||||
</span>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -87,7 +84,8 @@
|
|||
<button class="btn btn-primary">Submit</button>
|
||||
</form>
|
||||
|
||||
<p class="text-muted">Images and Icons may be cached, use <code>CNTRL + F5</code><sup>(google chrome hotkey)</sup> to reload without cache to see your changes appear :)</p>
|
||||
<p class="text-muted">Images and Icons may be cached, use <code>CNTRL + F5</code><sup>(google
|
||||
chrome hotkey)</sup> to reload without cache to see your changes appear :)</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
@ -103,7 +101,15 @@
|
|||
</section>
|
||||
<!-- END CONTENT -->
|
||||
|
||||
|
||||
<script>
|
||||
// Add the following code if you want the name of the file appear on select
|
||||
document.addEventListener('DOMContentLoaded', ()=>{
|
||||
$(".custom-file-input").on("change", function () {
|
||||
var fileName = $(this).val().split("\\").pop();
|
||||
$(this).siblings(".custom-file-label").addClass("selected").html(fileName);
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@endsection
|
||||
|
|
|
@ -46,7 +46,6 @@
|
|||
<th>Role</th>
|
||||
<th>Email</th>
|
||||
<th>{{CREDITS_DISPLAY_NAME}}</th>
|
||||
<th>Usage</th>
|
||||
<th>Servers</th>
|
||||
<th>Verified</th>
|
||||
<th>Last seen</th>
|
||||
|
@ -87,7 +86,6 @@
|
|||
{data: 'role'},
|
||||
{data: 'email', name: 'users.email'},
|
||||
{data: 'credits' , name : 'users.credits'},
|
||||
{data: 'usage' , sortable : false},
|
||||
{data: 'servers' , sortable : false},
|
||||
{data: 'verified' , sortable : false},
|
||||
{data: 'last_seen'},
|
||||
|
|
|
@ -184,6 +184,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-lg-6">
|
||||
<div class="row">
|
||||
<div class="col-lg-4">
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
href="{{\Illuminate\Support\Facades\Storage::disk('public')->exists('favicon.ico') ? asset('storage/favicon.ico') : asset('favicon.ico')}}"
|
||||
type="image/x-icon">
|
||||
|
||||
<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
|
||||
{{-- <link rel="stylesheet" href="{{asset('css/adminlte.min.css')}}">--}}
|
||||
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/v/bs4/dt-1.10.24/datatables.min.css"/>
|
||||
|
||||
|
@ -76,11 +78,33 @@
|
|||
</div>
|
||||
</li>
|
||||
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link" href="#" id="userDropdown" role="button" data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false">
|
||||
<span class="mr-1 d-lg-inline text-gray-600">
|
||||
<small><i class="fas fa-coins mr-2"></i></small>{{Auth::user()->credits()}}
|
||||
</span>
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-right shadow animated--grow-in" aria-labelledby="userDropdown">
|
||||
<a class="dropdown-item" href="{{route('store.index')}}">
|
||||
<i class="fas fa-coins fa-sm fa-fw mr-2 text-gray-400"></i>
|
||||
{{__('Store')}}
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" data-toggle="modal" data-target="#redeemVoucherModal"
|
||||
href="javascript:void(0)">
|
||||
<i class="fas fa-money-check-alt fa-sm fa-fw mr-2 text-gray-400"></i>
|
||||
{{__('Redeem code')}}
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="nav-item dropdown no-arrow">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button" data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false">
|
||||
<span class="mr-1 d-none d-lg-inline text-gray-600 small">
|
||||
<span class="mr-1 d-lg-inline text-gray-600 small">
|
||||
{{Auth::user()->name}}
|
||||
<img width="28px" height="28px" class="rounded-circle ml-1" src="{{Auth::user()->getAvatar()}}">
|
||||
</span>
|
||||
|
@ -102,11 +126,6 @@
|
|||
Log back in
|
||||
</a>
|
||||
@endif
|
||||
<a class="dropdown-item" data-toggle="modal" data-target="#redeemVoucherModal"
|
||||
href="javascript:void(0)">
|
||||
<i class="fas fa-money-check-alt fa-sm fa-fw mr-2 text-gray-400"></i>
|
||||
Redeem code
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<form method="post" action="{{route('logout')}}">
|
||||
@csrf
|
||||
|
@ -168,7 +187,42 @@
|
|||
@endif
|
||||
|
||||
@if(Auth::user()->role == 'admin')
|
||||
<li class="nav-header">Admin</li>
|
||||
|
||||
<li class="nav-header">Administration</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a href="{{route('admin.overview.index')}}"
|
||||
class="nav-link @if(Request::routeIs('admin.overview.*')) active @endif">
|
||||
<i class="nav-icon fa fa-home"></i>
|
||||
<p>Overview</p>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a href="{{route('admin.configurations.index')}}"
|
||||
class="nav-link @if(Request::routeIs('admin.configurations.*')) active @endif">
|
||||
<i class="nav-icon fas fa-cogs"></i>
|
||||
<p>Configurations</p>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a href="{{route('admin.settings.index')}}"
|
||||
class="nav-link @if(Request::routeIs('admin.settings.*')) active @endif">
|
||||
<i class="nav-icon fas fa-tools"></i>
|
||||
<p>Settings</p>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a href="{{route('admin.api.index')}}"
|
||||
class="nav-link @if(Request::routeIs('admin.api.*')) active @endif">
|
||||
<i class="nav-icon fa fa-gamepad"></i>
|
||||
<p>Application API</p>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-header">Management</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a href="{{route('admin.users.index')}}"
|
||||
|
@ -210,21 +264,32 @@
|
|||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-header">Pterodactyl</li>
|
||||
{{-- <li class="nav-header">Pterodactyl</li>--}}
|
||||
|
||||
{{-- <li class="nav-item">--}}
|
||||
{{-- <a href="{{route('admin.nodes.index')}}"--}}
|
||||
{{-- class="nav-link @if(Request::routeIs('admin.nodes.*')) active @endif">--}}
|
||||
{{-- <i class="nav-icon fas fa-sitemap"></i>--}}
|
||||
{{-- <p>Nodes</p>--}}
|
||||
{{-- </a>--}}
|
||||
{{-- </li>--}}
|
||||
|
||||
{{-- <li class="nav-item">--}}
|
||||
{{-- <a href="{{route('admin.nests.index')}}"--}}
|
||||
{{-- class="nav-link @if(Request::routeIs('admin.nests.*')) active @endif">--}}
|
||||
{{-- <i class="nav-icon fas fa-th-large"></i>--}}
|
||||
{{-- <p>Nests</p>--}}
|
||||
{{-- </a>--}}
|
||||
{{-- </li>--}}
|
||||
|
||||
|
||||
<li class="nav-header">Other</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a href="{{route('admin.nodes.index')}}"
|
||||
class="nav-link @if(Request::routeIs('admin.nodes.*')) active @endif">
|
||||
<i class="nav-icon fas fa-sitemap"></i>
|
||||
<p>Nodes</p>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a href="{{route('admin.nests.index')}}"
|
||||
class="nav-link @if(Request::routeIs('admin.nests.*')) active @endif">
|
||||
<i class="nav-icon fas fa-th-large"></i>
|
||||
<p>Nests</p>
|
||||
<a href="{{route('admin.usefullinks.index')}}"
|
||||
class="nav-link @if(Request::routeIs('admin.usefullinks.*')) active @endif">
|
||||
<i class="nav-icon fas fa-link"></i>
|
||||
<p>Useful Links</p>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
@ -248,43 +313,6 @@
|
|||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
<li class="nav-header">Dashboard</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a href="{{route('admin.api.index')}}"
|
||||
class="nav-link @if(Request::routeIs('admin.api.*')) active @endif">
|
||||
<i class="nav-icon fa fa-gamepad"></i>
|
||||
<p>Application API</p>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a href="{{route('admin.usefullinks.index')}}"
|
||||
class="nav-link @if(Request::routeIs('admin.usefullinks.*')) active @endif">
|
||||
<i class="nav-icon fas fa-link"></i>
|
||||
<p>Useful Links</p>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-header">Settings</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a href="{{route('admin.configurations.index')}}"
|
||||
class="nav-link @if(Request::routeIs('admin.configurations.*')) active @endif">
|
||||
<i class="nav-icon fas fa-cogs"></i>
|
||||
<p>Configurations</p>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a href="{{route('admin.settings.index')}}"
|
||||
class="nav-link @if(Request::routeIs('admin.settings.*')) active @endif">
|
||||
<i class="nav-icon fas fa-tools"></i>
|
||||
<p>Settings</p>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@endif
|
||||
|
||||
</ul>
|
||||
|
@ -340,6 +368,8 @@
|
|||
<script type="text/javascript" src="https://cdn.datatables.net/v/bs4/dt-1.10.24/datatables.min.js"></script>
|
||||
<!-- Summernote -->
|
||||
<script src="{{asset('plugins/summernote/summernote-bs4.min.js')}}"></script>
|
||||
<!-- select2 -->
|
||||
<script src="{{asset('plugins/select2/js/select2.min.js')}}"></script>
|
||||
|
||||
<!-- Moment.js -->
|
||||
<script src="{{asset('plugins/moment/moment.min.js')}}"></script>
|
||||
|
|
|
@ -6,7 +6,7 @@ Your payment has been confirmed; Your credit balance has been updated.<br>
|
|||
___
|
||||
### Payment ID: **{{$payment->id}}**<br>
|
||||
### Status: **{{$payment->status}}**<br>
|
||||
### Price: **{{$payment->formatCurrency()}}**<br>
|
||||
### Price: **{{$payment->formatToCurrency($payment->total_price)}}**<br>
|
||||
### Type: **{{$payment->type}}**<br>
|
||||
### Amount: **{{$payment->amount}}**<br>
|
||||
### Balance: **{{$payment->user->credits}}**<br>
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<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>
|
||||
href="{{ route('servers.create') }}">Create</a>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
@ -23,130 +23,450 @@
|
|||
<!-- END CONTENT HEADER -->
|
||||
|
||||
<!-- MAIN CONTENT -->
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
<section x-data="serverApp()" class="content">
|
||||
<div class="container">
|
||||
|
||||
<!-- FORM -->
|
||||
<form action="{{route('servers.store')}}" method="post" class="row">
|
||||
@csrf
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<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 )
|
||||
<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')}}
|
||||
@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
|
||||
|
||||
<!-- CUSTOM CONTENT -->
|
||||
<div class="row justify-content-center">
|
||||
<div class="card col-lg-8 col-md-12 mb-5">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title"><i class="fa fa-server mr-2"></i>Create Server</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post" action="{{ route('servers.store') }}">
|
||||
@csrf
|
||||
<div class="form-group">
|
||||
<label for="name">* Name</label>
|
||||
<input id="name" name="name" type="text" required="required"
|
||||
class="form-control @error('name') is-invalid @enderror">
|
||||
<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">
|
||||
Please fill out this field.
|
||||
</div>
|
||||
<div class="invalid-feedback">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="description">Description</label>
|
||||
<input id="description" name="description" type="text"
|
||||
class="form-control @error('description') is-invalid @enderror">
|
||||
|
||||
@error('description')
|
||||
<div class="invalid-feedback">
|
||||
Please fill out this field.
|
||||
<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>
|
||||
@enderror
|
||||
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="location_id">* Server location</label>
|
||||
<div>
|
||||
|
||||
<select id="node_id" name="node_id" required="required"
|
||||
class="custom-select @error('node_id') is-invalid @enderror">
|
||||
<option selected disabled hidden value="">Please Select ...</option>
|
||||
@foreach ($locations as $location)
|
||||
<optgroup label="{{ $location->name }}">
|
||||
@foreach ($location->nodes as $node)
|
||||
@if (!$node->disabled)
|
||||
<option value="{{ $node->id }}">{{ $node->name }}</option>
|
||||
@endif
|
||||
@endforeach
|
||||
</optgroup>
|
||||
@endforeach
|
||||
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@error('node_id')
|
||||
<div class="invalid-feedback">
|
||||
Please fill out this field.
|
||||
<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>
|
||||
@enderror
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="egg_id">* Server configuration</label>
|
||||
<div>
|
||||
<select id="egg_id" name="egg_id" required="required"
|
||||
class="custom-select @error('egg_id') is-invalid @enderror">
|
||||
<option selected disabled hidden value="">Please Select ...</option>
|
||||
@foreach ($nests as $nest)
|
||||
<optgroup label="{{ $nest->name }}">
|
||||
@foreach ($nest->eggs as $egg)
|
||||
<option value="{{ $egg->id }}">{{ $egg->name }}</option>
|
||||
@endforeach
|
||||
</optgroup>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@error('egg_id')
|
||||
<div class="invalid-feedback">
|
||||
Please fill out this field.
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="product_id">* Resource Configuration</label>
|
||||
<div>
|
||||
<select id="product_id" name="product_id" required="required"
|
||||
class="custom-select @error('product_id') is-invalid @enderror">
|
||||
<option selected disabled hidden value="">Please Select...</option>
|
||||
@foreach ($products as $product)
|
||||
<option value="{{ $product->id }}" @if ($product->minimum_credits == -1 && Auth::user()->credits >= $minimum_credits)
|
||||
@elseif ($product->minimum_credits != -1 && Auth::user()->credits >=
|
||||
$product->minimum_credits)
|
||||
@else
|
||||
disabled
|
||||
@endif
|
||||
>{{ $product->name }}
|
||||
({{ $product->description }})
|
||||
<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 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>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@error('product_id')
|
||||
<div class="invalid-feedback">
|
||||
Please fill out this field.
|
||||
</div>
|
||||
@enderror
|
||||
</template>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group text-right">
|
||||
<input type="submit" class="btn btn-primary mt-3" value="Submit"
|
||||
onclick="this.disabled=true;this.value='Creating, please wait...';this.form.submit();">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END CUSTOM CONTENT -->
|
||||
<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>
|
||||
</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')}} ({{__('ports')}})</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>
|
||||
</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()
|
||||
},
|
||||
|
||||
/**
|
||||
* @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;
|
||||
this.products = response.data
|
||||
|
||||
//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()
|
||||
},
|
||||
|
||||
/**
|
||||
* @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.locations.forEach(location => {
|
||||
this.selectedNodeObject = location.nodes.find(node => node.id == this.selectedNode) ?? {};
|
||||
})
|
||||
|
||||
this.selectedProductObject = this.products.find(product => product.id == this.selectedProduct) ?? {}
|
||||
},
|
||||
|
||||
/**
|
||||
* @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
|
||||
|
|
|
@ -83,7 +83,7 @@
|
|||
<td>1</td>
|
||||
<td><i class="fa fa-coins mr-2"></i>{{$product->quantity}} {{strtolower($product->type) == 'credits' ? CREDITS_DISPLAY_NAME : $product->type}}</td>
|
||||
<td>{{$product->description}}</td>
|
||||
<td>{{$product->formatCurrency()}}</td>
|
||||
<td>{{$product->formatToCurrency($product->price)}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -111,11 +111,11 @@
|
|||
<table class="table">
|
||||
<tr>
|
||||
<th style="width:50%">Subtotal:</th>
|
||||
<td>{{$product->formatCurrency()}}</td>
|
||||
<td>{{$product->formatToCurrency($product->price)}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Tax (0%)</th>
|
||||
<td>0.00</td>
|
||||
<th>Tax ({{$taxpercent}}%)</th>
|
||||
<td>{{$product->formatToCurrency($taxvalue)}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Quantity:</th>
|
||||
|
@ -123,7 +123,7 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<th>Total:</th>
|
||||
<td>{{$product->formatCurrency()}}</td>
|
||||
<td>{{$product->formatToCurrency($total)}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
<?php /** @var $product PaypalProduct */?>
|
||||
@foreach($products as $product)
|
||||
<tr>
|
||||
<td>{{$product->formatCurrency()}}</td>
|
||||
<td>{{$product->formatToCurrency($product->price)}}</td>
|
||||
<td>{{strtolower($product->type) == 'credits' ? CREDITS_DISPLAY_NAME : $product->type}}</td>
|
||||
<td><i class="fa fa-coins mr-2"></i>{{$product->display}}</td>
|
||||
<td><a href="{{route('checkout' , $product->id)}}" class="btn btn-info">Purchase</a>
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
use App\Http\Controllers\Admin\ActivityLogController;
|
||||
use App\Http\Controllers\Admin\ApplicationApiController;
|
||||
use App\Http\Controllers\Admin\ConfigurationController;
|
||||
use App\Http\Controllers\Admin\NestsController;
|
||||
use App\Http\Controllers\Admin\NodeController;
|
||||
use App\Http\Controllers\Admin\OverViewController;
|
||||
use App\Http\Controllers\Admin\PaymentController;
|
||||
use App\Http\Controllers\Admin\PaypalProductController;
|
||||
use App\Http\Controllers\Admin\ProductController;
|
||||
|
@ -16,6 +15,7 @@ use App\Http\Controllers\Admin\VoucherController;
|
|||
use App\Http\Controllers\Auth\SocialiteController;
|
||||
use App\Http\Controllers\HomeController;
|
||||
use App\Http\Controllers\NotificationController;
|
||||
use App\Http\Controllers\ProductController as FrontProductController;
|
||||
use App\Http\Controllers\ProfileController;
|
||||
use App\Http\Controllers\ServerController;
|
||||
use App\Http\Controllers\StoreController;
|
||||
|
@ -54,6 +54,12 @@ Route::middleware(['auth', 'checkSuspended'])->group(function () {
|
|||
Route::resource('profile', ProfileController::class);
|
||||
Route::resource('store', StoreController::class);
|
||||
|
||||
#server create utility routes (product)
|
||||
#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');
|
||||
|
||||
#payments
|
||||
Route::get('checkout/{paypalProduct}', [PaymentController::class, 'checkOut'])->name('checkout');
|
||||
Route::get('payment/success', [PaymentController::class, 'success'])->name('payment.success');
|
||||
|
@ -72,6 +78,9 @@ Route::middleware(['auth', 'checkSuspended'])->group(function () {
|
|||
#admin
|
||||
Route::prefix('admin')->name('admin.')->middleware('admin')->group(function () {
|
||||
|
||||
Route::get('overview', [OverViewController::class, 'index'])->name('overview.index');
|
||||
Route::get('overview/sync', [OverViewController::class, 'syncPterodactyl'])->name('overview.sync');
|
||||
|
||||
Route::resource('activitylogs', ActivityLogController::class);
|
||||
|
||||
Route::get("users.json", [UserController::class, "json"])->name('users.json');
|
||||
|
@ -87,6 +96,7 @@ Route::middleware(['auth', 'checkSuspended'])->group(function () {
|
|||
Route::resource('servers', AdminServerController::class);
|
||||
|
||||
Route::get('products/datatable', [ProductController::class, 'datatable'])->name('products.datatable');
|
||||
Route::get('products/clone/{product}', [ProductController::class, 'clone'])->name('products.clone');
|
||||
Route::patch('products/disable/{product}', [ProductController::class, 'disable'])->name('products.disable');
|
||||
Route::resource('products', ProductController::class);
|
||||
|
||||
|
@ -99,13 +109,13 @@ Route::middleware(['auth', 'checkSuspended'])->group(function () {
|
|||
Route::get('payments/datatable', [PaymentController::class, 'datatable'])->name('payments.datatable');
|
||||
Route::get('payments', [PaymentController::class, 'index'])->name('payments.index');
|
||||
|
||||
Route::get('nodes/datatable', [NodeController::class, 'datatable'])->name('nodes.datatable');
|
||||
Route::get('nodes/sync', [NodeController::class, 'sync'])->name('nodes.sync');
|
||||
Route::resource('nodes', NodeController::class);
|
||||
|
||||
Route::get('nests/datatable', [NestsController::class, 'datatable'])->name('nests.datatable');
|
||||
Route::get('nests/sync', [NestsController::class, 'sync'])->name('nests.sync');
|
||||
Route::resource('nests', NestsController::class);
|
||||
// Route::get('nodes/datatable', [NodeController::class, 'datatable'])->name('nodes.datatable');
|
||||
// Route::get('nodes/sync', [NodeController::class, 'sync'])->name('nodes.sync');
|
||||
// Route::resource('nodes', NodeController::class);
|
||||
//
|
||||
// Route::get('nests/datatable', [NestsController::class, 'datatable'])->name('nests.datatable');
|
||||
// Route::get('nests/sync', [NestsController::class, 'sync'])->name('nests.sync');
|
||||
// Route::resource('nests', NestsController::class);
|
||||
|
||||
Route::get('configurations/datatable', [ConfigurationController::class, 'datatable'])->name('configurations.datatable');
|
||||
Route::patch('configurations/updatevalue', [ConfigurationController::class, 'updatevalue'])->name('configurations.updatevalue');
|
||||
|
|
2
storage/debugbar/.gitignore
vendored
Normal file
2
storage/debugbar/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
Loading…
Add table
Reference in a new issue