Merge pull request #262 from ControlPanel-gg/development

Development
This commit is contained in:
AVMG 2021-11-03 20:53:13 +01:00 committed by GitHub
commit 93fdab5e76
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 1128 additions and 644 deletions

View file

@ -112,7 +112,7 @@ class ConfigurationController extends Controller
return datatables($query)
->addColumn('actions', function (Configuration $configuration) {
return '<button data-content="Edit" data-toggle="popover" data-trigger="hover" data-placement="top" onclick="configuration.parse(\'' . $configuration->key . '\',\'' . $configuration->value . '\')" data-content="Edit" data-trigger="hover" data-toggle="tooltip" class="btn btn-sm btn-info mr-1"><i class="fas fa-pen"></i></button> ';
return '<button data-content="Edit" data-toggle="popover" data-trigger="hover" data-placement="top" onclick="configuration.parse(\'' . $configuration->key . '\',\'' . $configuration->value . '\',\'' . $configuration->type . '\')" data-content="Edit" data-trigger="hover" data-toggle="tooltip" class="btn btn-sm btn-info mr-1"><i class="fas fa-pen"></i></button> ';
})
->editColumn('created_at', function (Configuration $configuration) {
return $configuration->created_at ? $configuration->created_at->diffForHumans() : '';

View file

@ -141,12 +141,12 @@ class PaymentController extends Controller
$user->increment('credits', $paypalProduct->quantity);
//update server limit
if (Configuration::getValueByKey('SERVER_LIMIT_AFTER_IRL_PURCHASE', 10) !== 0) {
if ($user->server_limit < Configuration::getValueByKey('SERVER_LIMIT_AFTER_IRL_PURCHASE', 10)) {
$user->update(['server_limit' => 10]);
if (Configuration::getValueByKey('SERVER_LIMIT_AFTER_IRL_PURCHASE') !== 0) {
if ($user->server_limit < Configuration::getValueByKey('SERVER_LIMIT_AFTER_IRL_PURCHASE')) {
$user->update(['server_limit' => Configuration::getValueByKey('SERVER_LIMIT_AFTER_IRL_PURCHASE')]);
}
}
//update role
if ($user->role == 'member') {
$user->update(['role' => 'client']);

View file

@ -3,6 +3,7 @@
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Configuration;
use App\Models\Product;
use Exception;
use Illuminate\Contracts\Foundation\Application;
@ -51,6 +52,7 @@ class ProductController extends Controller
"swap" => "required|numeric|max:1000000|min:0",
"description" => "required|string|max:191",
"disk" => "required|numeric|max:1000000|min:5",
"minimum_credits" => "required|numeric|max:1000000|min:-1",
"io" => "required|numeric|max:1000000|min:0",
"databases" => "required|numeric|max:1000000|min:0",
"backups" => "required|numeric|max:1000000|min:0",
@ -73,7 +75,8 @@ class ProductController extends Controller
public function show(Product $product)
{
return view('admin.products.show', [
'product' => $product
'product' => $product,
'minimum_credits' => Configuration::getValueByKey("MINIMUM_REQUIRED_CREDITS_TO_MAKE_SERVER"),
]);
}
@ -108,6 +111,7 @@ class ProductController extends Controller
"description" => "required|string|max:191",
"disk" => "required|numeric|max:1000000|min:5",
"io" => "required|numeric|max:1000000|min:0",
"minimum_credits" => "required|numeric|max:1000000|min:-1",
"databases" => "required|numeric|max:1000000|min:0",
"backups" => "required|numeric|max:1000000|min:0",
"allocations" => "required|numeric|max:1000000|min:0",
@ -125,7 +129,8 @@ class ProductController extends Controller
* @param Product $product
* @return RedirectResponse
*/
public function disable(Request $request, Product $product) {
public function disable(Request $request, Product $product)
{
$product->update(['disabled' => !$product->disabled]);
return redirect()->route('admin.products.index')->with('success', 'product has been updated!');
@ -181,12 +186,11 @@ class ProductController extends Controller
' . csrf_field() . '
' . method_field("PATCH") . '
<div class="custom-control custom-switch">
<input '.$checked.' name="disabled" onchange="this.form.submit()" type="checkbox" class="custom-control-input" id="switch'.$product->id.'">
<label class="custom-control-label" for="switch'.$product->id.'"></label>
<input ' . $checked . ' name="disabled" onchange="this.form.submit()" type="checkbox" class="custom-control-input" id="switch' . $product->id . '">
<label class="custom-control-label" for="switch' . $product->id . '"></label>
</div>
</form>
';
})
->editColumn('created_at', function (Product $product) {
return $product->created_at ? $product->created_at->diffForHumans() : '';

View file

@ -221,6 +221,20 @@ class UserController extends Controller
return redirect()->route('admin.users.notifications')->with('success', 'Notification sent!');
}
/**
* @param User $user
* @return RedirectResponse
*/
public function toggleSuspended(User $user){
try {
!$user->isSuspended() ? $user->suspend() : $user->unSuspend();
} catch (Exception $exception) {
return redirect()->back()->with('error', $exception->getMessage());
}
return redirect()->back()->with('success', 'User has been updated!');
}
/**
*
* @throws Exception
@ -252,10 +266,17 @@ class UserController extends Controller
return $user->last_seen ? $user->last_seen->diffForHumans() : '';
})
->addColumn('actions', function (User $user) {
$suspendColor = $user->isSuspended() ? "btn-success" : "btn-warning";
$suspendIcon = $user->isSuspended() ? "fa-play-circle" : "fa-pause-circle";
$suspendText = $user->isSuspended() ? "Unsuspend" : "Suspend";
return '
<a data-content="Login as user" data-toggle="popover" data-trigger="hover" data-placement="top" href="' . route('admin.users.loginas', $user->id) . '" class="btn btn-sm btn-primary mr-1"><i class="fas fa-sign-in-alt"></i></a>
<a data-content="Show" data-toggle="popover" data-trigger="hover" data-placement="top" href="' . route('admin.users.show', $user->id) . '" class="btn btn-sm text-white btn-warning mr-1"><i class="fas fa-eye"></i></a>
<a data-content="Edit" data-toggle="popover" data-trigger="hover" data-placement="top" href="' . route('admin.users.edit', $user->id) . '" class="btn btn-sm btn-info mr-1"><i class="fas fa-pen"></i></a>
<form class="d-inline" method="post" action="' . route('admin.users.togglesuspend', $user->id) . '">
' . csrf_field() . '
<button data-content="'.$suspendText.'" data-toggle="popover" data-trigger="hover" data-placement="top" class="btn btn-sm '.$suspendColor.' text-white mr-1"><i class="far '.$suspendIcon.'"></i></button>
</form>
<form class="d-inline" onsubmit="return submitResult();" method="post" action="' . route('admin.users.destroy', $user->id) . '">
' . csrf_field() . '
' . method_field("DELETE") . '

View file

@ -4,6 +4,7 @@ namespace App\Http\Controllers\Admin;
use App\Events\UserUpdateCreditsEvent;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Models\Voucher;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\View\Factory;
@ -115,6 +116,13 @@ class VoucherController extends Controller
return redirect()->back()->with('success', 'voucher has been removed!');
}
public function users(Voucher $voucher)
{
return view('admin.vouchers.users', [
'voucher' => $voucher
]);
}
/**
* @param Request $request
* @return JsonResponse
@ -144,7 +152,7 @@ class VoucherController extends Controller
]);
if ($request->user()->credits + $voucher->credits >= 99999999) throw ValidationException::withMessages([
'code' => "You can't redeem this voucher because you would exceed the ".CREDITS_DISPLAY_NAME." limit"
'code' => "You can't redeem this voucher because you would exceed the " . CREDITS_DISPLAY_NAME . " limit"
]);
#redeem voucher
@ -153,10 +161,27 @@ class VoucherController extends Controller
event(new UserUpdateCreditsEvent($request->user()));
return response()->json([
'success' => "{$voucher->credits} ".CREDITS_DISPLAY_NAME." have been added to your balance!"
'success' => "{$voucher->credits} " . CREDITS_DISPLAY_NAME . " have been added to your balance!"
]);
}
public function usersDataTable(Voucher $voucher)
{
$users = $voucher->users();
return datatables($users)
->editColumn('name', function (User $user) {
return '<a class="text-info" target="_blank" href="' . route('admin.users.show', $user->id) . '">' . $user->name . '</a>';
})
->addColumn('credits', function (User $user) {
return '<i class="fas fa-coins mr-2"></i> ' . $user->credits();
})
->addColumn('last_seen', function (User $user) {
return $user->last_seen ? $user->last_seen->diffForHumans() : '';
})
->rawColumns(['name', 'credits', 'last_seen'])
->make();
}
public function dataTable()
{
$query = Voucher::query();
@ -164,6 +189,7 @@ class VoucherController extends Controller
return datatables($query)
->addColumn('actions', function (Voucher $voucher) {
return '
<a data-content="Users" data-toggle="popover" data-trigger="hover" data-placement="top" href="' . route('admin.vouchers.users', $voucher->id) . '" class="btn btn-sm btn-primary mr-1"><i class="fas fa-users"></i></a>
<a data-content="Edit" data-toggle="popover" data-trigger="hover" data-placement="top" href="' . route('admin.vouchers.edit', $voucher->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.vouchers.destroy', $voucher->id) . '">

View file

@ -6,11 +6,13 @@ use App\Http\Controllers\Controller;
use App\Models\DiscordUser;
use App\Models\User;
use App\Notifications\DynamicNotification;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\HtmlString;
use Illuminate\Validation\ValidationException;
use Spatie\ValidationRules\Rules\Delimited;
class NotificationController extends Controller
@ -55,19 +57,21 @@ class NotificationController extends Controller
*
* @param Request $request
* @return JsonResponse
* @throws ValidationException
*/
public function send(Request $request)
{
$data = $request->validate([
"via" => ["required", new Delimited("in:mail,database")],
"all" => "required_without:users|boolean",
"users" => ["required_without:all", new Delimited("exists:users,id")],
"users" => ["required_without:all"],
"title" => "required|string|min:1",
"content" => "required|string|min:1"
]);
$via = explode(",", $data["via"]);
$mail = null;
$database = null;
if (in_array("database", $via)) {
$database = [
"title" => $data["title"],
@ -79,10 +83,28 @@ class NotificationController extends Controller
->subject($data["title"])
->line(new HtmlString($data["content"]));
}
$all = $data["all"] ?? false;
$users = $all ? User::all() : User::whereIn("id", explode(",", $data["users"]))->get();
if ($all) {
$users = User::all();
} else {
$userIds = explode(",", $data["users"]);
$users = User::query()
->whereIn("id", $userIds)
->orWhereHas('discordUser', function (Builder $builder) use ($userIds) {
$builder->whereIn('id', $userIds);
})
->get();
}
if ($users->count() == 0) {
throw ValidationException::withMessages([
'users' => ['No users found!'],
]);
}
Notification::send($users, new DynamicNotification($via, $database, $mail));
return response()->json(["message" => "Notification successfully sent."]);
return response()->json(["message" => "Notification successfully sent.", 'user_count' => $users->count()]);
}
/**

View file

@ -6,11 +6,17 @@ use App\Http\Controllers\Controller;
use App\Models\Server;
use Exception;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Spatie\QueryBuilder\QueryBuilder;
class ServerController extends Controller
{
public const ALLOWED_INCLUDES = ['product', 'user'];
public const ALLOWED_FILTERS = ['name', 'suspended', 'identifier', 'pterodactyl_id', 'user_id', 'product_id'];
/**
* Display a listing of the resource.
*
@ -19,21 +25,28 @@ class ServerController extends Controller
*/
public function index(Request $request)
{
return Server::with('product')->paginate($request->query('per_page') ?? 50);
}
$query = QueryBuilder::for(Server::class)
->allowedIncludes(self::ALLOWED_INCLUDES)
->allowedFilters(self::ALLOWED_FILTERS);
return $query->paginate($request->input('per_page') ?? 50);
}
/**
* Display the specified resource.
*
* @param Server $server
* @return Server
*
* @return Server|Collection|Model
*/
public function show(Server $server)
{
return $server->load('product');
}
$query = QueryBuilder::for(Server::class)
->where('id', '=', $server->id)
->allowedIncludes(self::ALLOWED_INCLUDES);
return $query->firstOrFail();
}
/**
* Remove the specified resource from storage.

View file

@ -2,28 +2,45 @@
namespace App\Http\Controllers\Api;
use App\Classes\Pterodactyl;
use App\Events\UserUpdateCreditsEvent;
use App\Http\Controllers\Controller;
use App\Models\Configuration;
use App\Models\DiscordUser;
use App\Models\User;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Contracts\Routing\ResponseFactory;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use Spatie\QueryBuilder\QueryBuilder;
class UserController extends Controller
{
const ALLOWED_INCLUDES = ['servers', 'notifications', 'payments', 'vouchers', 'discordUser'];
const ALLOWED_FILTERS = ['name', 'server_limit', 'email', 'pterodactyl_id', 'role', 'suspended'];
/**
* Display a listing of the resource.
*
* @param Request $request
* @return Response
* @return LengthAwarePaginator
*/
public function index(Request $request)
{
return User::paginate($request->query('per_page') ?? 50);
$query = QueryBuilder::for(User::class)
->allowedIncludes(self::ALLOWED_INCLUDES)
->allowedFilters(self::ALLOWED_FILTERS);
return $query->paginate($request->input('per_page') ?? 50);
}
@ -31,12 +48,23 @@ class UserController extends Controller
* Display the specified resource.
*
* @param int $id
* @return User
*
* @return User|Builder|Collection|Model
*/
public function show(int $id)
{
$discordUser = DiscordUser::find($id);
return $discordUser ? $discordUser->user : User::findOrFail($id);
$user = $discordUser ? $discordUser->user : User::findOrFail($id);
$query = QueryBuilder::for($user)
->with('discordUser')
->allowedIncludes(self::ALLOWED_INCLUDES)
->where('users.id', '=', $id)
->orWhereHas('discordUser', function (Builder $builder) use ($id) {
$builder->where('id', '=', $id);
});
return $query->firstOrFail();
}
@ -53,11 +81,11 @@ class UserController extends Controller
$user = $discordUser ? $discordUser->user : User::findOrFail($id);
$request->validate([
"name" => "sometimes|string|min:4|max:30",
"email" => "sometimes|string|email",
"credits" => "sometimes|numeric|min:0|max:1000000",
"name" => "sometimes|string|min:4|max:30",
"email" => "sometimes|string|email",
"credits" => "sometimes|numeric|min:0|max:1000000",
"server_limit" => "sometimes|numeric|min:0|max:1000000",
"role" => ['sometimes', Rule::in(['admin', 'mod', 'client', 'member'])],
"role" => ['sometimes', Rule::in(['admin', 'mod', 'client', 'member'])],
]);
$user->update($request->all());
@ -81,23 +109,23 @@ class UserController extends Controller
$user = $discordUser ? $discordUser->user : User::findOrFail($id);
$request->validate([
"credits" => "sometimes|numeric|min:0|max:1000000",
"credits" => "sometimes|numeric|min:0|max:1000000",
"server_limit" => "sometimes|numeric|min:0|max:1000000",
]);
if($request->credits){
if ($user->credits + $request->credits >= 99999999) throw ValidationException::withMessages([
if ($request->credits) {
if ($user->credits + $request->credits >= 99999999) throw ValidationException::withMessages([
'credits' => "You can't add this amount of credits because you would exceed the credit limit"
]);
event(new UserUpdateCreditsEvent($user));
$user->increment('credits', $request->credits);
}
}
if($request->server_limit){
if ($request->server_limit) {
if ($user->server_limit + $request->server_limit >= 2147483647) throw ValidationException::withMessages([
'server_limit' => "You cannot add this amount of servers because it would exceed the server limit."
]);
$user->increment('server_limit', $request->server_limit);
$user->increment('server_limit', $request->server_limit);
}
return $user;
@ -117,27 +145,72 @@ class UserController extends Controller
$user = $discordUser ? $discordUser->user : User::findOrFail($id);
$request->validate([
"credits" => "sometimes|numeric|min:0|max:1000000",
"credits" => "sometimes|numeric|min:0|max:1000000",
"server_limit" => "sometimes|numeric|min:0|max:1000000",
]);
if($request->credits){
if($user->credits - $request->credits < 0) throw ValidationException::withMessages([
if ($request->credits) {
if ($user->credits - $request->credits < 0) throw ValidationException::withMessages([
'credits' => "You can't remove this amount of credits because you would exceed the minimum credit limit"
]);
$user->decrement('credits', $request->credits);
}
}
if($request->server_limit){
if($user->server_limit - $request->server_limit < 0) throw ValidationException::withMessages([
if ($request->server_limit) {
if ($user->server_limit - $request->server_limit < 0) throw ValidationException::withMessages([
'server_limit' => "You cannot remove this amount of servers because it would exceed the minimum server."
]);
$user->decrement('server_limit', $request->server_limit);
$user->decrement('server_limit', $request->server_limit);
}
return $user;
}
/**
* @throws ValidationException
*/
public function store(Request $request)
{
$request->validate([
'name' => ['required', 'string', 'max:30', 'min:4', 'alpha_num', 'unique:users'],
'email' => ['required', 'string', 'email', 'max:64', 'unique:users'],
'password' => ['required', 'string', 'min:8', 'max:191'],
]);
$user = User::create([
'name' => $request->input('name'),
'email' => $request->input('email'),
'credits' => Configuration::getValueByKey('INITIAL_CREDITS', 150),
'server_limit' => Configuration::getValueByKey('INITIAL_SERVER_LIMIT', 1),
'password' => Hash::make($request->input('password')),
]);
$response = Pterodactyl::client()->post('/application/users', [
"external_id" => App::environment('local') ? Str::random(16) : (string)$user->id,
"username" => $user->name,
"email" => $user->email,
"first_name" => $user->name,
"last_name" => $user->name,
"password" => $request->input('password'),
"root_admin" => false,
"language" => "en"
]);
if ($response->failed()) {
$user->delete();
throw ValidationException::withMessages([
'pterodactyl_error_message' => $response->toException()->getMessage(),
'pterodactyl_error_status' => $response->toException()->getCode()
]);
}
$user->update([
'pterodactyl_id' => $response->json()['attributes']['id']
]);
return $user;
}
/**
* Remove the specified resource from storage.
*

View file

@ -5,20 +5,30 @@ namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Voucher;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Validation\Rule;
use Spatie\QueryBuilder\QueryBuilder;
class VoucherController extends Controller
{
const ALLOWED_INCLUDES = ['users'];
const ALLOWED_FILTERS = ['code', 'memo', 'credits', 'uses'];
/**
* Display a listing of the resource.
*
* @return Response
* @return LengthAwarePaginator
*/
public function index(Request $request)
{
return Voucher::paginate($request->query('per_page') ?? 50);
$query = QueryBuilder::for(Voucher::class)
->allowedIncludes(self::ALLOWED_INCLUDES)
->allowedFilters(self::ALLOWED_FILTERS);
return $query->paginate($request->input('per_page') ?? 50);
}
/**
@ -40,10 +50,10 @@ class VoucherController extends Controller
public function store(Request $request)
{
$request->validate([
'memo' => 'nullable|string|max:191',
'code' => 'required|string|alpha_dash|max:36|min:4|unique:vouchers',
'uses' => 'required|numeric|max:2147483647|min:1',
'credits' => 'required|numeric|between:0,99999999',
'memo' => 'nullable|string|max:191',
'code' => 'required|string|alpha_dash|max:36|min:4|unique:vouchers',
'uses' => 'required|numeric|max:2147483647|min:1',
'credits' => 'required|numeric|between:0,99999999',
'expires_at' => 'nullable|multiple_date_format:d-m-Y H:i:s,d-m-Y|after:now|before:10 years'
]);
@ -54,11 +64,16 @@ class VoucherController extends Controller
* Display the specified resource.
*
* @param int $id
* @return Response
*
* @return Voucher|Collection|Model
*/
public function show(int $id)
{
return Voucher::findOrFail($id);
$query = QueryBuilder::for(Voucher::class)
->where('id', '=', $id)
->allowedIncludes(self::ALLOWED_INCLUDES);
return $query->firstOrFail();
}
/**
@ -84,10 +99,10 @@ class VoucherController extends Controller
$voucher = Voucher::findOrFail($id);
$request->validate([
'memo' => 'nullable|string|max:191',
'code' => "required|string|alpha_dash|max:36|min:4|unique:vouchers,code,{$voucher->id}",
'uses' => 'required|numeric|max:2147483647|min:1',
'credits' => 'required|numeric|between:0,99999999',
'memo' => 'nullable|string|max:191',
'code' => "required|string|alpha_dash|max:36|min:4|unique:vouchers,code,{$voucher->id}",
'uses' => 'required|numeric|max:2147483647|min:1',
'credits' => 'required|numeric|between:0,99999999',
'expires_at' => 'nullable|multiple_date_format:d-m-Y H:i:s,d-m-Y|after:now|before:10 years'
]);

View file

@ -1,30 +1,108 @@
<?php
namespace App\Http\Controllers;
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_OUT_OF_CREDITS_TEXT = "You ran out of Credits";
public function __construct()
{
$this->middleware('auth');
}
/**
* @description Get the Background Color for the Days-Left-Box in HomeView
*
* @param float $days
*
* @return string
*/
public function getTimeLeftBoxBackground(float $days)
{
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;
}
}
/**
* @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)
{
if ($days < 1)
{
if ($hours < 1)
{
return $this::TIME_LEFT_OUT_OF_CREDITS_TEXT;
}
else
{
return strval($hours);
}
}
return strval(number_format($days, 0));
}
/** Show the application dashboard. */
public function index(Request $request)
{
$usage = 0;
$usage = Auth::user()->creditUsage();
$credits = Auth::user()->Credits();
$bg = "";
$boxText = "";
$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';
foreach (Auth::user()->servers as $server){
$usage += $server->product->price;
}
// RETURN ALL VALUES
return view('home')->with([
'useage' => $usage,
'useful_links' => UsefulLink::all()->sortBy('id')
'credits' => $credits,
'useful_links' => UsefulLink::all()->sortBy('id'),
'bg' => $bg,
'boxText' => $boxText,
'unit' => $unit
]);
}
}

View file

@ -16,7 +16,8 @@ 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
{
@ -39,6 +40,7 @@ class ServerController extends Controller
$query->where('disabled', '=', false);
})->get(),
'nests' => Nest::where('disabled', '=', false)->get(),
'minimum_credits' => Configuration::getValueByKey('MINIMUM_REQUIRED_CREDITS_TO_MAKE_SERVER', 50)
]);
}
@ -52,7 +54,7 @@ class ServerController extends Controller
"description" => "nullable|max:191",
"node_id" => "required|exists:nodes,id",
"egg_id" => "required|exists:eggs,id",
"product_id" => "required|exists:products,id",
"product_id" => "required|exists:products,id"
]);
//get required resources
@ -74,8 +76,8 @@ class ServerController extends Controller
'identifier' => $response->json()['attributes']['identifier']
]);
if (Configuration::getValueByKey('SERVER_CREATE_CHARGE_FIRST_HOUR' , 'true') == 'true'){
if (Auth::user()->credits >= $server->product->getHourlyPrice()){
if (Configuration::getValueByKey('SERVER_CREATE_CHARGE_FIRST_HOUR', 'true') == 'true') {
if (Auth::user()->credits >= $server->product->getHourlyPrice()) {
Auth::user()->decrement('credits', $server->product->getHourlyPrice());
}
}
@ -86,15 +88,24 @@ class ServerController extends Controller
/**
* @return null|RedirectResponse
*/
private function validateConfigurationRules(){
private function validateConfigurationRules()
{
//limit validation
if (Auth::user()->servers()->count() >= Auth::user()->server_limit) {
return redirect()->route('servers.index')->with('error', 'Server limit reached!');
}
//minimum credits
if (Auth::user()->credits <= Configuration::getValueByKey('MINIMUM_REQUIRED_CREDITS_TO_MAKE_SERVER', 50)) {
return redirect()->route('servers.index')->with('error', "You do not have the required amount of ".CREDITS_DISPLAY_NAME." to create a new server!");
// minimum credits
if (FacadesRequest::has("product_id")) {
$product = Product::findOrFail(FacadesRequest::input("product_id"));
if (
Auth::user()->credits <
($product->minimum_credits == -1
? Configuration::getValueByKey('MINIMUM_REQUIRED_CREDITS_TO_MAKE_SERVER', 50)
: $product->minimum_credits)
) {
return redirect()->route('servers.index')->with('error', "You do not have the required amount of " . CREDITS_DISPLAY_NAME . " to use this product!");
}
}
//Required Verification for creating an server
@ -141,7 +152,7 @@ class ServerController extends Controller
* @param Server $server
* @return RedirectResponse
*/
private function serverCreationFailed(Response $response , Server $server)
private function serverCreationFailed(Response $response, Server $server)
{
$server->delete();

View file

@ -3,6 +3,7 @@
namespace App\Http;
use App\Http\Middleware\ApiAuthToken;
use App\Http\Middleware\CheckSuspended;
use App\Http\Middleware\CreditsDisplayName;
use App\Http\Middleware\isAdmin;
use App\Http\Middleware\LastSeen;
@ -42,7 +43,7 @@ class Kernel extends HttpKernel
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
LastSeen::class,
CreditsDisplayName::class
CreditsDisplayName::class,
],
'api' => [
@ -70,6 +71,7 @@ class Kernel extends HttpKernel
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'admin' => isAdmin::class,
'api.token' => ApiAuthToken::class
'api.token' => ApiAuthToken::class,
'checkSuspended' => CheckSuspended::class
];
}

View file

@ -0,0 +1,28 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class CheckSuspended
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
if (auth()->check() && auth()->user()->isSuspended()) {
auth()->logout();
$message = 'Your account has been suspended. Please contact our support team!';
return redirect()->route('login')->withMessage($message);
}
return $next($request);
}
}

View file

@ -6,7 +6,6 @@ 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 Spatie\Activitylog\Traits\LogsActivity;
class Product extends Model
@ -17,10 +16,11 @@ class Product extends Model
protected $guarded = ['id'];
public static function boot() {
public static function boot()
{
parent::boot();
static::creating(function(Product $product) {
static::creating(function (Product $product) {
$client = new Client();
$product->{$product->getKeyName()} = $client->generateId($size = 21);
@ -47,6 +47,6 @@ class Product extends Model
*/
public function servers(): BelongsTo
{
return $this->belongsTo(Server::class , 'id' , 'product_id');
return $this->belongsTo(Server::class, 'id', 'product_id');
}
}

View file

@ -59,7 +59,8 @@ class User extends Authenticatable implements MustVerifyEmail
'password',
'pterodactyl_id',
'discord_verified_at',
'avatar'
'avatar',
'suspended'
];
/**
@ -79,7 +80,9 @@ class User extends Authenticatable implements MustVerifyEmail
*/
protected $casts = [
'email_verified_at' => 'datetime',
'last_seen' => 'datetime',
'last_seen' => 'datetime',
'credits' => 'float',
'server_limit' => 'float',
];
/**
@ -94,13 +97,13 @@ class User extends Authenticatable implements MustVerifyEmail
});
static::deleting(function (User $user) {
$user->servers()->chunk(10 , function ($servers) {
$user->servers()->chunk(10, function ($servers) {
foreach ($servers as $server) {
$server->delete();
}
});
$user->payments()->chunk(10 , function ($payments) {
$user->payments()->chunk(10, function ($payments) {
foreach ($payments as $payment) {
$payment->delete();
}
@ -114,6 +117,38 @@ class User extends Authenticatable implements MustVerifyEmail
});
}
/**
* @return HasMany
*/
public function servers()
{
return $this->hasMany(Server::class);
}
/**
* @return HasMany
*/
public function payments()
{
return $this->hasMany(Payment::class);
}
/**
* @return BelongsToMany
*/
public function vouchers()
{
return $this->belongsToMany(Voucher::class);
}
/**
* @return HasOne
*/
public function discordUser()
{
return $this->hasOne(DiscordUser::class);
}
/**
*
*/
@ -130,11 +165,67 @@ class User extends Authenticatable implements MustVerifyEmail
return number_format($this->credits, 2, '.', '');
}
/**
* @return bool
*/
public function isSuspended()
{
return $this->suspended;
}
/**
*
* @throws Exception
*/
public function suspend()
{
foreach ($this->servers as $server) {
$server->suspend();
}
$this->update([
'suspended' => true
]);
return $this;
}
/**
* @throws Exception
*/
public function unSuspend()
{
foreach ($this->servers as $server) {
if ($this->credits >= $server->product->getHourlyPrice()) {
$server->unSuspend();
}
}
$this->update([
'suspended' => false
]);
return $this;
}
/**
* @return string
*/
public function getAvatar(){
public function getAvatar()
{
//TODO loading the images to confirm they exist is causing to much load time. alternative has to be found :) maybe onerror tag on the <img tags>
// if ($this->discordUser()->exists()) {
// if(@getimagesize($this->discordUser->getAvatar())) {
// $avatar = $this->discordUser->getAvatar();
// } else {
// $avatar = "https://www.gravatar.com/avatar/" . md5(strtolower(trim($this->email)));
// }
// } else {
// $avatar = "https://www.gravatar.com/avatar/" . md5(strtolower(trim($this->email)));
// }
return "https://www.gravatar.com/avatar/" . md5(strtolower(trim($this->email)));
}
/**
@ -144,7 +235,7 @@ class User extends Authenticatable implements MustVerifyEmail
{
$usage = 0;
foreach ($this->Servers as $server){
foreach ($this->Servers as $server) {
$usage += $server->product->price;
}
@ -154,42 +245,12 @@ class User extends Authenticatable implements MustVerifyEmail
/**
* @return array|string|string[]
*/
public function getVerifiedStatus(){
public function getVerifiedStatus()
{
$status = '';
if ($this->hasVerifiedEmail()) $status .= 'email ';
if ($this->discordUser()->exists()) $status .= 'discord';
$status = str_replace(' ' , '/' , $status);
$status = str_replace(' ', '/', $status);
return $status;
}
/**
* @return BelongsToMany
*/
public function vouchers(){
return $this->belongsToMany(Voucher::class);
}
/**
* @return HasOne
*/
public function discordUser(){
return $this->hasOne(DiscordUser::class);
}
/**
* @return HasMany
*/
public function servers()
{
return $this->hasMany(Server::class);
}
/**
* @return HasMany
*/
public function payments()
{
return $this->hasMany(Payment::class);
}
}

View file

@ -31,7 +31,17 @@ class Voucher extends Model
'expires_at'
];
protected $appends = ['used' , 'status'];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'credits' => 'float',
'uses' => 'integer'
];
protected $appends = ['used', 'status'];
/**
* @return int
@ -44,7 +54,8 @@ class Voucher extends Model
/**
* @return string
*/
public function getStatusAttribute(){
public function getStatusAttribute()
{
return $this->getStatus();
}

View file

@ -37,7 +37,26 @@ class WelcomeMessage extends Notification implements ShouldQueue
{
return ['database'];
}
public function AdditionalLines()
{
$AdditionalLine = "";
if(Configuration::getValueByKey('CREDITS_REWARD_AFTER_VERIFY_EMAIL') != 0) {
$AdditionalLine .= "Verifying your e-mail address will grant you ".Configuration::getValueByKey('CREDITS_REWARD_AFTER_VERIFY_EMAIL')." additional " . Configuration::getValueByKey('CREDITS_DISPLAY_NAME') . ". <br />";
}
if(Configuration::getValueByKey('SERVER_LIMIT_REWARD_AFTER_VERIFY_EMAIL') != 0) {
$AdditionalLine .= "Verifying your e-mail will also increase your Server Limit by " . Configuration::getValueByKey('SERVER_LIMIT_REWARD_AFTER_VERIFY_EMAIL') . ". <br />";
}
$AdditionalLine .="<br />";
if(Configuration::getValueByKey('CREDITS_REWARD_AFTER_VERIFY_DISCORD') != 0) {
$AdditionalLine .= "You can also verify your discord account to get another " . Configuration::getValueByKey('CREDITS_REWARD_AFTER_VERIFY_DISCORD') . " " . Configuration::getValueByKey('CREDITS_DISPLAY_NAME') . ". <br />";
}
if(Configuration::getValueByKey('SERVER_LIMIT_REWARD_AFTER_VERIFY_DISCORD') != 0) {
$AdditionalLine .= "Verifying your Discord account will also increase your Server Limit by " . Configuration::getValueByKey('SERVER_LIMIT_REWARD_AFTER_VERIFY_DISCORD') . ". <br />";
}
return $AdditionalLine;
}
/**
* Get the array representation of the notification.
*
@ -51,7 +70,10 @@ class WelcomeMessage extends Notification implements ShouldQueue
'content' => "
<p>Hello <strong>{$this->user->name}</strong>, Welcome to our dashboard!</p>
<h5>Verification</h5>
<p>Please verify your email address to get " . Configuration::getValueByKey('CREDITS_REWARD_AFTER_VERIFY_EMAIL') . " extra credits and increase your server limit to " . Configuration::getValueByKey('SERVER_LIMIT_REWARD_AFTER_VERIFY_EMAIL') . "<br />You can also verify your discord account to get another " . Configuration::getValueByKey('CREDITS_REWARD_AFTER_VERIFY_DISCORD') . " credits and to increase your server limit again with " . Configuration::getValueByKey('SERVER_LIMIT_REWARD_AFTER_VERIFY_DISCORD') . "</p>
<p>You can verify your e-mail address and link/verify your Discord account.</p>
<p>
".$this->AdditionalLines()."
</p>
<h5>Information</h5>
<p>This dashboard can be used to create and delete servers.<br /> These servers can be used and managed on our pterodactyl panel.<br /> If you have any questions, please join our Discord server and #create-a-ticket.</p>
<p>We hope you can enjoy this hosting experience and if you have any suggestions please let us know!</p>

View file

@ -29,7 +29,6 @@ class AppServiceProvider extends ServiceProvider
{
Paginator::useBootstrap();
Schema::defaultStringLength(191);
QueryBuilderRequest::setArrayValueDelimiter('|');
Validator::extend('multiple_date_format', function ($attribute, $value, $parameters, $validator) {

View file

@ -23,7 +23,7 @@
"paypal/rest-api-sdk-php": "^1.14",
"socialiteproviders/discord": "^4.1",
"spatie/laravel-activitylog": "^3.16",
"spatie/laravel-query-builder": "^3.5",
"spatie/laravel-query-builder": "^3.6",
"spatie/laravel-validation-rules": "^3.0",
"yajra/laravel-datatables-oracle": "~9.0"
},

14
composer.lock generated
View file

@ -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": "b3b61a46d5d4d6560d052cfda863d12c",
"content-hash": "f7ba581ff6641d3ab79d558070e99f3c",
"packages": [
{
"name": "asm89/stack-cors",
@ -3462,16 +3462,16 @@
},
{
"name": "spatie/laravel-query-builder",
"version": "3.5.0",
"version": "3.6.0",
"source": {
"type": "git",
"url": "https://github.com/spatie/laravel-query-builder.git",
"reference": "4e5257be24139836dc092f618d7c73bcb1c00302"
"reference": "03d8e1307dcb58b16fcc9c4947633fc60ae74802"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/laravel-query-builder/zipball/4e5257be24139836dc092f618d7c73bcb1c00302",
"reference": "4e5257be24139836dc092f618d7c73bcb1c00302",
"url": "https://api.github.com/repos/spatie/laravel-query-builder/zipball/03d8e1307dcb58b16fcc9c4947633fc60ae74802",
"reference": "03d8e1307dcb58b16fcc9c4947633fc60ae74802",
"shasum": ""
},
"require": {
@ -3528,7 +3528,7 @@
"type": "custom"
}
],
"time": "2021-07-05T14:17:44+00:00"
"time": "2021-09-06T08:03:10+00:00"
},
{
"name": "spatie/laravel-validation-rules",
@ -8713,5 +8713,5 @@
"ext-intl": "*"
},
"platform-dev": [],
"plugin-api-version": "2.0.0"
"plugin-api-version": "2.1.0"
}

View file

@ -2,7 +2,7 @@
return [
'version' => '0.5',
'version' => '0.6',
/*
|--------------------------------------------------------------------------
| Application Name

46
config/query-builder.php Normal file
View file

@ -0,0 +1,46 @@
<?php
/**
* @see https://github.com/spatie/laravel-query-builder
*/
return [
/*
* By default the package will use the `include`, `filter`, `sort`
* and `fields` query parameters as described in the readme.
*
* You can customize these query string parameters here.
*/
'parameters' => [
'include' => 'include',
'filter' => 'filter',
'sort' => 'sort',
'fields' => 'fields',
'append' => 'append',
],
/*
* Related model counts are included using the relationship name suffixed with this string.
* For example: GET /users?include=postsCount
*/
'count_suffix' => 'Count',
/*
* By default the package will throw an `InvalidFilterQuery` exception when a filter in the
* URL is not allowed in the `allowedFilters()` method.
*/
'disable_invalid_filter_query_exception' => false,
/*
* By default the package inspects query string of request using $request->query().
* You can change this behavior to inspect the request body using $request->input()
* by setting this value to `body`.
*
* Possible values: `query_string`, `body`
*/
'request_data_source' => 'query_string',
];

View file

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddSuspendedToUsersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->boolean('suspended')->default(false);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('suspended');
});
}
}

View file

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddProductMinimumCredits extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('products', function (Blueprint $table) {
$table->float('minimum_credits')->default(-1);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('products', function (Blueprint $table) {
$table->dropColumn('minimum_credits');
});
}
}

View file

@ -71,7 +71,10 @@
<tbody>
@foreach($logs as $log)
<tr>
<td>{{$log->causer ? json_decode($log->causer)->name : 'system'}}</td>
<td> @if($log->causer) <a href='/admin/users/{{$log->causer_id}}'> {{json_decode($log->causer)->name}}
@else
System
@endif</td>
<td>
<span>
@switch($log->description)
@ -90,6 +93,18 @@
@endswitch
{{ucfirst($log->description)}}
{{ explode("\\" , $log->subject_type)[2]}}
@php $first=true @endphp
@foreach(json_decode($log->properties, true) as $properties)
@if($first)
@if(isset($properties['name']))
" {{$properties['name']}} "
@endif
@if(isset($properties['email']))
< {{$properties['email']}} >
@endif
@php $first=false @endphp
@endif
@endforeach
</span>
</td>

View file

@ -22,7 +22,6 @@
<i class="fa fa-cog"></i>
</div>
</div>
<input id="value" name="value" type="text" class="form-control" required="required">
</div>
</div>
@ -37,16 +36,26 @@
<button type="submit" class="btn btn-primary">Save</button>
</div>
</form>
</div>
</div>
</div>
<script>
window.configuration = {
parse(key, value){
parse(key, value, type) {
$('#keyLabel').html(key)
$('#key').val(key)
$('#value').remove();
if (type === 'integer') {
$('.input-group').append('<input id="value" name="value" type="number" class="form-control" required="required">')
} else if (type === 'boolean') {
$('.input-group').append('<select id="value" name="value" class="form-control" required=required>' +
'<option value="true">true</option>' +
'<option value="false">false</option>' +
'</select>')
} else if (type === 'string') {
$('.input-group').append('<input id="value" name="value" type="text" class="form-control" required="required">')
}
$('#value').val(value)
$('#editConfigurationModel').modal('show')
}

View file

@ -10,9 +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 class="text-muted" href="{{route('admin.products.create')}}">Create</a>
<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>
</li>
</ol>
</div>
@ -29,12 +30,16 @@
<div class="col-lg-6">
<div class="card">
<div class="card-body">
<form action="{{route('admin.products.store')}}" method="POST">
<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" data-content="Will hide this option from being selected" class="fas fa-info-circle"></i></label>
<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"
data-content="Will hide this option from being selected"
class="fas fa-info-circle"></i></label>
</div>
</div>
@ -42,78 +47,75 @@
<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="{{ 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_DISPLAY_NAME }}</label>
<input value="{{ 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="{{ 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="{{ 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="{{ 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>
<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>
@error('description')
<div class="invalid-feedback">
{{$message}}
</div>
<div class="invalid-feedback">
{{ $message }}
</div>
@enderror
</div>
@ -121,65 +123,74 @@
<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="{{ 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>
<div class="form-group">
<label for="minimum_credits">Minimum {{ CREDITS_DISPLAY_NAME }} <i
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">
@error('minimum_credits')
<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="{{ 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="{{ 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="{{ 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="{{ 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>
@ -200,6 +211,11 @@
</section>
<!-- END CONTENT -->
<script>
document.addEventListener('DOMContentLoaded', function() {
$('[data-toggle="popover"]').popover();
});
</script>
@endsection

View file

@ -10,9 +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 class="text-muted" href="{{route('admin.products.edit' , $product->id)}}">Edit</a>
<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>
</li>
</ol>
</div>
@ -29,22 +30,28 @@
<div class="row">
<div class="col-lg-6">
@if($product->servers()->count() > 0)
@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>
<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-body">
<form action="{{route('admin.products.update' , $product->id)}}" method="POST">
<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" data-content="Will hide this option from being selected" class="fas fa-info-circle"></i></label>
<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>
</div>
@ -52,78 +59,74 @@
<div class="col-lg-6">
<div class="form-group">
<label for="name">Name</label>
<input value="{{$product->name}}" id="name" name="name" type="text"
class="form-control @error('name') is-invalid @enderror"
required="required">
<input value="{{ $product->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="{{$product->price}}" id="price" name="price"
type="number"
class="form-control @error('price') is-invalid @enderror"
required="required">
<label for="price">Price in {{ CREDITS_DISPLAY_NAME }}</label>
<input value="{{ $product->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="{{$product->memory}}" id="memory" name="memory"
type="number"
class="form-control @error('memory') is-invalid @enderror"
required="required">
<input value="{{ $product->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="{{$product->cpu}}" id="cpu" name="cpu"
type="number"
class="form-control @error('cpu') is-invalid @enderror"
required="required">
<input value="{{ $product->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="{{$product->swap}}" id="swap" name="swap"
type="number"
class="form-control @error('swap') is-invalid @enderror"
required="required">
<input value="{{ $product->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">{{$product->description}}</textarea>
<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>
@error('description')
<div class="invalid-feedback">
{{$message}}
</div>
<div class="invalid-feedback">
{{ $message }}
</div>
@enderror
</div>
@ -131,65 +134,72 @@
<div class="col-lg-6">
<div class="form-group">
<label for="disk">Disk</label>
<input value="{{$product->disk}}" id="disk" name="disk"
type="number"
class="form-control @error('disk') is-invalid @enderror"
required="required">
<input value="{{ $product->disk }}" 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>
<div class="form-group">
<label for="minimum_credits">Minimum {{ CREDITS_DISPLAY_NAME }} <i
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="{{ $product->minimum_credits }}" 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>
@enderror
</div>
<div class="form-group">
<label for="io">IO</label>
<input value="{{$product->io}}" id="io" name="io"
type="number"
class="form-control @error('io') is-invalid @enderror"
required="required">
<input value="{{ $product->io }}" 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="{{$product->databases}}" id="databases"
name="databases"
type="number"
class="form-control @error('databases') is-invalid @enderror"
required="required">
<input value="{{ $product->databases }}" 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="{{$product->backups}}" id="backups"
name="backups"
type="number"
class="form-control @error('backups') is-invalid @enderror"
required="required">
<input value="{{ $product->backups }}" 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="{{$product->allocations}}"
id="allocations" name="allocations"
type="number"
class="form-control @error('allocations') is-invalid @enderror"
required="required">
<input value="{{ $product->allocations }}" 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>
@ -210,6 +220,11 @@
</section>
<!-- END CONTENT -->
<script>
document.addEventListener('DOMContentLoaded', function() {
$('[data-toggle="popover"]').popover();
});
</script>
@endsection

View file

@ -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.users.index')}}">Products</a></li>
<li class="breadcrumb-item"><a href="{{ route('home') }}">Dashboard</a></li>
<li class="breadcrumb-item"><a href="{{ route('admin.users.index') }}">Products</a></li>
<li class="breadcrumb-item"><a class="text-muted"
href="{{route('admin.products.show' , $product->id)}}">Show</a>
href="{{ route('admin.products.show', $product->id) }}">Show</a>
</li>
</ol>
</div>
@ -30,11 +30,15 @@
<div class="card-header d-flex justify-content-between">
<h5 class="card-title"><i class="fas fa-sliders-h mr-2"></i>Product</h5>
<div class="ml-auto">
<a data-content="Edit" data-trigger="hover" data-toggle="tooltip" 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) }}">
<a data-content="Edit" data-trigger="hover" data-toggle="tooltip"
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) }}">
{{ csrf_field() }}
{{ method_field("DELETE") }}
<button data-content="Delete" data-trigger="hover" data-toggle="tooltip" class="btn btn-sm btn-danger mr-1"><i class="fas fa-trash"></i></button>
{{ method_field('DELETE') }}
<button data-content="Delete" data-trigger="hover" data-toggle="tooltip"
class="btn btn-sm btn-danger mr-1"><i class="fas fa-trash"></i></button>
</form>
</div>
</div>
@ -47,9 +51,9 @@
<label>ID</label>
</div>
<div class="col-lg-8">
<span style="max-width: 250px;" class="d-inline-block text-truncate">
{{$product->id}}
</span>
<span style="max-width: 250px;" class="d-inline-block text-truncate">
{{ $product->id }}
</span>
</div>
</div>
</div>
@ -60,9 +64,9 @@
<label>Name</label>
</div>
<div class="col-lg-8">
<span style="max-width: 250px;" class="d-inline-block text-truncate">
{{$product->name}}
</span>
<span style="max-width: 250px;" class="d-inline-block text-truncate">
{{ $product->name }}
</span>
</div>
</div>
</div>
@ -73,9 +77,26 @@
<label>Price</label>
</div>
<div class="col-lg-8">
<span style="max-width: 250px;" class="d-inline-block text-truncate">
<i class="fas fa-coins mr-1"></i>{{$product->price}}
</span>
<span style="max-width: 250px;" class="d-inline-block text-truncate">
<i class="fas fa-coins mr-1"></i>{{ $product->price }}
</span>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="row">
<div class="col-lg-4">
<label>Minimum {{ CREDITS_DISPLAY_NAME }}</label>
</div>
<div class="col-lg-8">
<span style="max-width: 250px;" class="d-inline-block text-truncate">
@if ($product->minimum_credits == -1)
<i class="fas fa-coins mr-1"></i>{{ $minimum_credits }}
@else
<i class="fas fa-coins mr-1"></i>{{ $product->minimum_credits }}
@endif
</span>
</div>
</div>
</div>
@ -87,9 +108,9 @@
<label>Memory</label>
</div>
<div class="col-lg-8">
<span style="max-width: 250px;" class="d-inline-block text-truncate">
{{$product->memory}}
</span>
<span style="max-width: 250px;" class="d-inline-block text-truncate">
{{ $product->memory }}
</span>
</div>
</div>
</div>
@ -100,9 +121,9 @@
<label>CPU</label>
</div>
<div class="col-lg-8">
<span style="max-width: 250px;" class="d-inline-block text-truncate">
{{$product->cpu}}
</span>
<span style="max-width: 250px;" class="d-inline-block text-truncate">
{{ $product->cpu }}
</span>
</div>
</div>
</div>
@ -113,9 +134,9 @@
<label>Swap</label>
</div>
<div class="col-lg-8">
<span style="max-width: 250px;" class="d-inline-block text-truncate">
{{$product->swap}}
</span>
<span style="max-width: 250px;" class="d-inline-block text-truncate">
{{ $product->swap }}
</span>
</div>
</div>
</div>
@ -126,9 +147,9 @@
<label>Disk</label>
</div>
<div class="col-lg-8">
<span style="max-width: 250px;" class="d-inline-block text-truncate">
{{$product->disk}}
</span>
<span style="max-width: 250px;" class="d-inline-block text-truncate">
{{ $product->disk }}
</span>
</div>
</div>
</div>
@ -139,9 +160,9 @@
<label>IO</label>
</div>
<div class="col-lg-8">
<span style="max-width: 250px;" class="d-inline-block text-truncate">
{{$product->io}}
</span>
<span style="max-width: 250px;" class="d-inline-block text-truncate">
{{ $product->io }}
</span>
</div>
</div>
</div>
@ -152,9 +173,9 @@
<label>Databases</label>
</div>
<div class="col-lg-8">
<span style="max-width: 250px;" class="d-inline-block text-truncate">
{{$product->databases}}
</span>
<span style="max-width: 250px;" class="d-inline-block text-truncate">
{{ $product->databases }}
</span>
</div>
</div>
</div>
@ -165,9 +186,9 @@
<label>Allocations</label>
</div>
<div class="col-lg-8">
<span style="max-width: 250px;" class="d-inline-block text-truncate">
{{$product->allocations}}
</span>
<span style="max-width: 250px;" class="d-inline-block text-truncate">
{{ $product->allocations }}
</span>
</div>
</div>
</div>
@ -178,9 +199,9 @@
<label>Created At</label>
</div>
<div class="col-lg-8">
<span style="max-width: 250px;" class="d-inline-block text-truncate">
{{$product->created_at ? $product->created_at->diffForHumans() : ''}}
</span>
<span style="max-width: 250px;" class="d-inline-block text-truncate">
{{ $product->created_at ? $product->created_at->diffForHumans() : '' }}
</span>
</div>
</div>
</div>
@ -192,9 +213,9 @@
<label>Description</label>
</div>
<div class="col-lg-8">
<span class="d-inline-block text-truncate">
{{$product->description}}
</span>
<span class="d-inline-block text-truncate">
{{ $product->description }}
</span>
</div>
</div>
</div>
@ -206,9 +227,9 @@
<label>Updated At</label>
</div>
<div class="col-lg-8">
<span style="max-width: 250px;" class="d-inline-block text-truncate">
{{$product->updated_at ? $product->updated_at->diffForHumans() : ''}}
</span>
<span style="max-width: 250px;" class="d-inline-block text-truncate">
{{ $product->updated_at ? $product->updated_at->diffForHumans() : '' }}
</span>
</div>
</div>
</div>

View file

@ -1,240 +0,0 @@
@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>Products</h1>
</div>
<div class="col-sm-6">
<ol class="breadcrumb float-sm-right">
<li class="breadcrumb-item"><a href="{{route('home')}}">Dashboard</a></li>
<li class="breadcrumb-item"><a href="{{route('admin.users.index')}}">Products</a></li>
<li class="breadcrumb-item"><a class="text-muted"
href="{{route('admin.products.show' , $product->id)}}">Show</a>
</li>
</ol>
</div>
</div>
</div>
</section>
<!-- END CONTENT HEADER -->
<!-- MAIN CONTENT -->
<section class="content">
<div class="container-fluid">
<div class="card">
<div class="card-header d-flex justify-content-between">
<h5 class="card-title"><i class="fas fa-sliders-h mr-2"></i>Product</h5>
<div class="ml-auto">
<a data-content="Edit" data-trigger="hover" data-toggle="tooltip" 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) }}">
{{ csrf_field() }}
{{ method_field("DELETE") }}
<button data-content="Delete" data-trigger="hover" data-toggle="tooltip" class="btn btn-sm btn-danger mr-1"><i class="fas fa-trash"></i></button>
</form>
</div>
</div>
<div class="card-body">
<div class="row">
<div class="col-lg-6">
<div class="row">
<div class="col-lg-4">
<label>ID</label>
</div>
<div class="col-lg-8">
<span style="max-width: 250px;" class="d-inline-block text-truncate">
{{$product->id}}
</span>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="row">
<div class="col-lg-4">
<label>Name</label>
</div>
<div class="col-lg-8">
<span style="max-width: 250px;" class="d-inline-block text-truncate">
{{$product->name}}
</span>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="row">
<div class="col-lg-4">
<label>Price</label>
</div>
<div class="col-lg-8">
<span style="max-width: 250px;" class="d-inline-block text-truncate">
<i class="fas fa-coins mr-1"></i>{{$product->price}}
</span>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="row">
<div class="col-lg-4">
<label>Memory</label>
</div>
<div class="col-lg-8">
<span style="max-width: 250px;" class="d-inline-block text-truncate">
{{$product->memory}}
</span>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="row">
<div class="col-lg-4">
<label>CPU</label>
</div>
<div class="col-lg-8">
<span style="max-width: 250px;" class="d-inline-block text-truncate">
{{$product->cpu}}
</span>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="row">
<div class="col-lg-4">
<label>Swap</label>
</div>
<div class="col-lg-8">
<span style="max-width: 250px;" class="d-inline-block text-truncate">
{{$product->swap}}
</span>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="row">
<div class="col-lg-4">
<label>Disk</label>
</div>
<div class="col-lg-8">
<span style="max-width: 250px;" class="d-inline-block text-truncate">
{{$product->disk}}
</span>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="row">
<div class="col-lg-4">
<label>IO</label>
</div>
<div class="col-lg-8">
<span style="max-width: 250px;" class="d-inline-block text-truncate">
{{$product->io}}
</span>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="row">
<div class="col-lg-4">
<label>Databases</label>
</div>
<div class="col-lg-8">
<span style="max-width: 250px;" class="d-inline-block text-truncate">
{{$product->databases}}
</span>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="row">
<div class="col-lg-4">
<label>Allocations</label>
</div>
<div class="col-lg-8">
<span style="max-width: 250px;" class="d-inline-block text-truncate">
{{$product->allocations}}
</span>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="row">
<div class="col-lg-4">
<label>Created At</label>
</div>
<div class="col-lg-8">
<span style="max-width: 250px;" class="d-inline-block text-truncate">
{{$product->created_at ? $product->created_at->diffForHumans() : ''}}
</span>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="row">
<div class="col-lg-4">
<label>Description</label>
</div>
<div class="col-lg-8">
<span class="d-inline-block text-truncate">
{{$product->description}}
</span>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="row">
<div class="col-lg-4">
<label>Updated At</label>
</div>
<div class="col-lg-8">
<span style="max-width: 250px;" class="d-inline-block text-truncate">
{{$product->updated_at ? $product->updated_at->diffForHumans() : ''}}
</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<h5 class="card-title"><i class="fas fa-server mr-2"></i>Servers</h5>
</div>
<div class="card-body table-responsive">
@include('admin.servers.table' , ['filter' => '?product=' . $product->id])
</div>
</div>
</div>
<!-- END CUSTOM CONTENT -->
</div>
</section>
<!-- END CONTENT -->
@endsection

View file

@ -0,0 +1,93 @@
@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>Vouchers</h1>
</div>
<div class="col-sm-6">
<ol class="breadcrumb float-sm-right">
<li class="breadcrumb-item"><a href="{{ route('home') }}">Dashboard</a></li>
<li class="breadcrumb-item"><a href="{{ route('admin.vouchers.index') }}">Vouchers</a></li>
<li class="breadcrumb-item"><a class="text-muted"
href="{{ route('admin.vouchers.users', $voucher->id) }}">Users</a>
</ol>
</div>
</div>
</div>
</section>
<!-- END CONTENT HEADER -->
<!-- MAIN CONTENT -->
<section class="content">
<div class="container-fluid">
<div class="card">
<div class="card-header">
<div class="d-flex justify-content-between">
<h5 class="card-title"><i class="fas fa-users mr-2"></i>Users</h5>
</div>
</div>
<div class="card-body table-responsive">
<table id="datatable" class="table table-striped">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
<th>{{ CREDITS_DISPLAY_NAME }}</th>
<th>Last Seen</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
<!-- END CUSTOM CONTENT -->
</section>
<!-- END CONTENT -->
<script>
document.addEventListener("DOMContentLoaded", function() {
$('#datatable').DataTable({
processing: true,
serverSide: true,
stateSave: true,
ajax: "{{ route('admin.vouchers.usersdatatable', $voucher->id) }}",
columns: [{
data: 'id'
}, {
data: 'name'
},
{
data: 'email'
},
{
data: 'credits'
},
{
data: 'last_seen'
},
],
fnDrawCallback: function(oSettings) {
$('[data-toggle="popover"]').popover();
}
});
});
</script>
@endsection

View file

@ -11,6 +11,10 @@
<div class="card-body">
<p class="login-box-msg">Sign in to start your session</p>
@if (session('message'))
<div class="alert alert-danger">{{ session('message') }}</div>
@endif
<form action="{{route('login')}}" method="post">
@csrf
@if(Session::has('error'))

View file

@ -38,7 +38,7 @@
<!-- /.col -->
<div class="col-12 col-sm-6 col-md-3">
<div class="info-box mb-3">
<span class="info-box-icon bg-danger elevation-1"><i class="fas fa-coins"></i></span>
<span class="info-box-icon bg-secondary elevation-1"><i class="fas fa-coins"></i></span>
<div class="info-box-content">
<span class="info-box-text">{{CREDITS_DISPLAY_NAME}}</span>
@ -65,11 +65,28 @@
</div>
<!-- /.info-box -->
</div>
<!-- /.col -->
@if($credits > 0.01 and $useage > 0)
<div class="col-12 col-sm-6 col-md-3">
<div class="info-box mb-3">
<span class="info-box-icon {{$bg}} elevation-1">
<i class="fas fa-hourglass-half"></i></span>
<div class="info-box-content">
<span class="info-box-text">Out of {{CREDITS_DISPLAY_NAME}} in </span>
<span class="info-box-number">{{$boxText}}<sup>{{$unit}}</sup></span>
</div>
</div>
<!-- /.info-box -->
@endif
</div>
<!-- /.col -->
</div>
<div class="row">
<div class="col-md-6">
<div class="card card-default">

View file

@ -1,16 +1,16 @@
@component('mail::message')
# Thank you for your purchase!
Your payment has been confirmed; Your credit balance has been updated.
Your payment has been confirmed; Your credit balance has been updated.<br>
# Details
___
### Payment ID: **{{$payment->id}}**
### Status: **{{$payment->status}}**
### Price: **{{$payment->formatCurrency()}}**
### Type: **{{$payment->type}}**
### Amount: **{{$payment->amount}}**
### Balance: **{{$payment->user->credits}}**
### User ID: **{{$payment->user_id}}**
### Payment ID: **{{$payment->id}}**<br>
### Status: **{{$payment->status}}**<br>
### Price: **{{$payment->formatCurrency()}}**<br>
### Type: **{{$payment->type}}**<br>
### Amount: **{{$payment->amount}}**<br>
### Balance: **{{$payment->user->credits}}**<br>
### User ID: **{{$payment->user_id}}**<br>
<br>
Thanks,<br>

View file

@ -213,6 +213,23 @@
<p>You are verified!</p>
</div>
</div>
<div class="row">
<div class="small-box bg-dark">
<div class="d-flex justify-content-between">
<div class="p-3">
<h3>{{$user->discordUser->username}} <sup>{{$user->discordUser->locale}}</sup> </h3>
<p>{{$user->discordUser->id}}
</p>
</div>
<div class="p-3"><img width="100px" height="100px" class="rounded-circle" src="{{$user->discordUser->getAvatar()}}" alt="avatar"></div>
</div>
<div class="small-box-footer">
<a href="{{route('auth.redirect')}}">
<i class="fab fa-discord mr-1"></i>Re-Sync Discord
</a>
</div>
</div>
</div>
@endif
</div>

View file

@ -10,9 +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('servers.index')}}">Servers</a>
<li class="breadcrumb-item"><a class="text-muted" href="{{route('servers.create')}}">Create</a>
<li class="breadcrumb-item"><a href="{{ route('home') }}">Dashboard</a></li>
<li class="breadcrumb-item"><a href="{{ route('servers.index') }}">Servers</a>
<li class="breadcrumb-item"><a class="text-muted"
href="{{ route('servers.create') }}">Create</a>
</li>
</ol>
</div>
@ -32,29 +33,29 @@
<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')}}">
<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">
class="form-control @error('name') is-invalid @enderror">
@error('name')
<div class="invalid-feedback">
Please fill out this field.
</div>
<div class="invalid-feedback">
Please fill out this field.
</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">
class="form-control @error('description') is-invalid @enderror">
@error('description')
<div class="invalid-feedback">
Please fill out this field.
</div>
<div class="invalid-feedback">
Please fill out this field.
</div>
@enderror
</div>
@ -63,13 +64,13 @@
<div>
<select id="node_id" name="node_id" required="required"
class="custom-select @error('node_id') is-invalid @enderror">
@foreach($locations as $location)
<optgroup label="{{$location->name}}">
@foreach($location->nodes as $node)
@if(!$node->disabled)
<option value="{{$node->id}}">{{$node->name}}</option>
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>
@ -79,20 +80,21 @@
</div>
@error('node_id')
<div class="invalid-feedback">
Please fill out this field.
</div>
<div class="invalid-feedback">
Please fill out this field.
</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">
@foreach($nests as $nest)
<optgroup label="{{$nest->name}}">
@foreach($nest->eggs as $egg)
<option value="{{$egg->id}}">{{$egg->name}}</option>
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
@ -100,32 +102,40 @@
</div>
@error('egg_id')
<div class="invalid-feedback">
Please fill out this field.
</div>
<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">
@foreach($products as $product)
<option value="{{$product->id}}">{{$product->name}}
({{$product->description}})
</option>
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 }})
</option>
@endforeach
</select>
</div>
@error('product_id')
<div class="invalid-feedback">
Please fill out this field.
</div>
<div class="invalid-feedback">
Please fill out this field.
</div>
@enderror
</div>
<div class="form-group text-right">
<button type="submit" class="btn btn-primary mt-3">Submit</button>
<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>

View file

@ -83,6 +83,14 @@
<td>Backups</td>
<td>{{$server->product->backups}}</td>
</tr>
<tr>
<td>Price per Hour</td>
<td>{{number_format($server->product->getHourlyPrice(),2,".", "")}} {{CREDITS_DISPLAY_NAME}}</td>
</tr>
<tr>
<td>Price per Month</td>
<td>{{$server->product->getHourlyPrice()*24*30}} {{CREDITS_DISPLAY_NAME}}</td>
</tr>
</table>
</div>

View file

@ -42,7 +42,7 @@
<!-- info row -->
<div class="row invoice-info">
<div class="col-sm-4 invoice-col">
From
To
<address>
<strong>{{config('app.name' , 'Laravel')}}</strong><br>
Email: {{env('PAYPAL_EMAIL' , env('MAIL_FROM_NAME'))}}
@ -50,7 +50,7 @@
</div>
<!-- /.col -->
<div class="col-sm-4 invoice-col">
To
From
<address>
<strong>{{Auth::user()->name}}</strong><br>
Email: {{Auth::user()->email}}

View file

@ -20,7 +20,7 @@ use Illuminate\Support\Facades\Route;
Route::middleware('api.token')->group(function () {
Route::patch('/users/{user}/increment', [UserController::class, 'increment']);
Route::patch('/users/{user}/decrement', [UserController::class, 'decrement']);
Route::resource('users', UserController::class)->except(['store', 'create']);
Route::resource('users', UserController::class)->except(['create']);
Route::patch('/servers/{server}/suspend', [ServerController::class, 'suspend']);
Route::patch('/servers/{server}/unsuspend', [ServerController::class, 'unSuspend']);

View file

@ -40,7 +40,7 @@ Route::middleware('guest')->get('/', function () {
Auth::routes(['verify' => true]);
Route::middleware('auth')->group(function () {
Route::middleware(['auth', 'checkSuspended'])->group(function () {
#resend verification email
Route::get('/email/verification-notification', function (Request $request) {
$request->user()->sendEmailVerificationNotification();
@ -79,6 +79,7 @@ Route::middleware('auth')->group(function () {
Route::get('users/datatable', [UserController::class, 'datatable'])->name('users.datatable');
Route::get('users/notifications', [UserController::class, 'notifications'])->name('users.notifications');
Route::post('users/notifications', [UserController::class, 'notify'])->name('users.notifications');
Route::post('users/togglesuspend/{user}', [UserController::class, 'toggleSuspended'])->name('users.togglesuspend');
Route::resource('users', UserController::class);
Route::get('servers/datatable', [AdminServerController::class, 'datatable'])->name('servers.datatable');
@ -118,6 +119,8 @@ Route::middleware('auth')->group(function () {
Route::resource('usefullinks', UsefulLinkController::class);
Route::get('vouchers/datatable', [VoucherController::class, 'datatable'])->name('vouchers.datatable');
Route::get('vouchers/{voucher}/usersdatatable', [VoucherController::class, 'usersdatatable'])->name('vouchers.usersdatatable');
Route::get('vouchers/{voucher}/users', [VoucherController::class, 'users'])->name('vouchers.users');
Route::resource('vouchers', VoucherController::class);
Route::get('api/datatable', [ApplicationApiController::class, 'datatable'])->name('api.datatable');