diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b856fd7b..bd7eee349 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,24 @@ This file is a running track of new features and fixes to each version of the pa This project follows [Semantic Versioning](http://semver.org) guidelines. +## v0.6.0-pre.5 (Courageous Carniadactylus) +### Changed +* New theme applied to Admin CP. Many graphical changes were made, some data was moved around and some display data changed. Too much was changed to feasibly log it all in here. Major breaking changes or notable new features will be logged. +* New server creation page now makes significantly less AJAX calls and is much quicker to respond. +* Server and Node view pages wee modified to split tabs into individual pages to make re-themeing and modifications significantly easier, and reduce MySQL query loads on page. + +### Fixed +* Fixes potential bug with invalid CIDR notation (ex: `192.168.1.1/z`) when adding allocations that could cause over 4 million records to be created at once. +* `[pre.4]` — Fixes bug preventing server updates from occurring by the system due to undefined `Auth::user()` in the event listener. +* `[pre.4]` — Fixes `Server::byUuid()` caching to actually clear the cache for *all* users, rather than the logged in user by using cache tags. + +### Added +* Ability to assign multiple allocations at once when creating a new server. + +### Deprecated +* Old API calls to `Server::create` will fail due to changed data structure. +* Many old routes were modified to reflect new standards in panel, and many of the controller functions being called were also modified. This shouldn't really impact anyone unless you have been digging into the code and modifying things. + ## v0.6.0-pre.4 (Courageous Carniadactylus) ### Fixed * `[pre.3]` — Fixes bug in cache handler that doesn't cache against the user making the request. Would have allowed for users to access servers not belonging to themselves in production. diff --git a/app/Http/Controllers/Admin/NodesController.php b/app/Http/Controllers/Admin/NodesController.php index 877c44995..4bb08c52f 100644 --- a/app/Http/Controllers/Admin/NodesController.php +++ b/app/Http/Controllers/Admin/NodesController.php @@ -27,8 +27,7 @@ namespace Pterodactyl\Http\Controllers\Admin; use DB; use Log; use Alert; -use Carbon; -use Validator; +use Javascript; use Pterodactyl\Models; use Illuminate\Http\Request; use Pterodactyl\Exceptions\DisplayException; @@ -39,57 +38,58 @@ use Pterodactyl\Exceptions\DisplayValidationException; class NodesController extends Controller { /** - * Controller Constructor. + * Displays the index page listing all nodes on the panel. + * + * @param Request $request + * @return \Illuminate\View\View */ - public function __construct() + public function index(Request $request) { - // + $nodes = Models\Node::with('location')->withCount('servers'); + + if (! is_null($request->input('query'))) { + $nodes->search($request->input('query')); + } + + return view('admin.nodes.index', ['nodes' => $nodes->paginate(25)]); } - public function getScript(Request $request, $id) + /** + * Displays create new node page. + * + * @param Request $request + * @return \Illuminate\View\View|\Illuminate\Response\RedirectResponse + */ + public function new(Request $request) { - return response()->view('admin.nodes.remote.deploy', [ - 'node' => Models\Node::findOrFail($id), - ])->header('Content-Type', 'text/plain'); - } - - public function getIndex(Request $request) - { - return view('admin.nodes.index', [ - 'nodes' => Models\Node::with('location')->withCount('servers')->paginate(20), - ]); - } - - public function getNew(Request $request) - { - if (! Models\Location::all()->count()) { + $locations = Models\Location::all(); + if (! $locations->count()) { Alert::warning('You must add a location before you can add a new node.')->flash(); return redirect()->route('admin.locations'); } - return view('admin.nodes.new', [ - 'locations' => Models\Location::all(), - ]); + return view('admin.nodes.new', ['locations' => $locations]); } - public function postNew(Request $request) + /** + * Post controller to create a new node on the system. + * + * @param Request $request + * @return \Illuminate\Response\RedirectResponse + */ + public function create(Request $request) { try { $repo = new NodeRepository; - $node = $repo->create($request->only([ - 'name', 'location_id', 'public', - 'fqdn', 'scheme', 'memory', - 'memory_overallocate', 'disk', - 'disk_overallocate', 'daemonBase', - 'daemonSFTP', 'daemonListen', + $node = $repo->create($request->intersect([ + 'name', 'location_id', 'public', 'fqdn', 'scheme', 'memory', + 'memory_overallocate', 'disk', 'disk_overallocate', + 'daemonBase', 'daemonSFTP', 'daemonListen', ])); Alert::success('Successfully created new node that can be configured automatically on your remote machine by visiting the configuration tab. Before you can add any servers you need to first assign some IP addresses and ports.')->flash(); - return redirect()->route('admin.nodes.view', [ - 'id' => $node->id, - 'tab' => 'tab_allocation', - ]); + return redirect()->route('admin.nodes.view', $node->id); } catch (DisplayValidationException $e) { return redirect()->route('admin.nodes.new')->withErrors(json_decode($e->getMessage()))->withInput(); } catch (DisplayException $e) { @@ -102,57 +102,148 @@ class NodesController extends Controller return redirect()->route('admin.nodes.new')->withInput(); } - public function getView(Request $request, $id) + /** + * Shows the index overview page for a specific node. + * + * @param Request $request + * @param int $id The ID of the node to display information for. + * + * @return \Illuminate\View\View + */ + public function viewIndex(Request $request, $id) { - $node = Models\Node::with( - 'servers.user', 'servers.service', - 'servers.allocations', 'location' - )->findOrFail($id); - $node->setRelation('allocations', $node->allocations()->with('server')->paginate(40)); + $node = Models\Node::with('location')->withCount('servers')->findOrFail($id); + $stats = collect( + Models\Server::select( + DB::raw('SUM(memory) as memory, SUM(disk) as disk') + )->where('node_id', $node->id)->first() + )->mapWithKeys(function ($item, $key) use ($node) { + $percent = ($item / $node->{$key}) * 100; - return view('admin.nodes.view', [ - 'node' => $node, - 'stats' => Models\Server::select(DB::raw('SUM(memory) as memory, SUM(disk) as disk'))->where('node_id', $node->id)->first(), + return [$key => [ + 'value' => $item, + 'percent' => $percent, + 'css' => ($percent <= 75) ? 'green' : (($percent > 90) ? 'red' : 'yellow'), + ]]; + })->toArray(); + + return view('admin.nodes.view.index', ['node' => $node, 'stats' => $stats]); + } + + /** + * Shows the settings page for a specific node. + * + * @param Request $request + * @param int $id The ID of the node to display information for. + * + * @return \Illuminate\View\View + */ + public function viewSettings(Request $request, $id) + { + return view('admin.nodes.view.settings', [ + 'node' => Models\Node::findOrFail($id), 'locations' => Models\Location::all(), ]); } - public function postView(Request $request, $id) + /** + * Shows the configuration page for a specific node. + * + * @param Request $request + * @param int $id The ID of the node to display information for. + * + * @return \Illuminate\View\View + */ + public function viewConfiguration(Request $request, $id) { + return view('admin.nodes.view.configuration', [ + 'node' => Models\Node::findOrFail($id), + ]); + } + + /** + * Shows the allocation page for a specific node. + * + * @param Request $request + * @param int $id The ID of the node to display information for. + * + * @return \Illuminate\View\View + */ + public function viewAllocation(Request $request, $id) + { + $node = Models\Node::findOrFail($id); + $node->setRelation('allocations', $node->allocations()->orderBy('ip', 'asc')->orderBy('port', 'asc')->with('server')->paginate(50)); + + Javascript::put([ + 'node' => collect($node)->only(['id']), + ]); + + return view('admin.nodes.view.allocation', ['node' => $node]); + } + + /** + * Shows the server listing page for a specific node. + * + * @param Request $request + * @param int $id The ID of the node to display information for. + * + * @return \Illuminate\View\View + */ + public function viewServers(Request $request, $id) + { + $node = Models\Node::with('servers.user', 'servers.service', 'servers.option')->findOrFail($id); + Javascript::put([ + 'node' => collect($node->makeVisible('daemonSecret'))->only(['scheme', 'fqdn', 'daemonListen', 'daemonSecret']), + ]); + + return view('admin.nodes.view.servers', [ + 'node' => $node, + ]); + } + + /** + * Updates settings for a node. + * + * @param Request $request + * @param int $node + * @return \Illuminate\Http\RedirectResponse + */ + public function updateSettings(Request $request, $id) + { + $repo = new NodeRepository; + try { - $node = new NodeRepository; - $node->update($id, $request->only([ - 'name', 'location_id', 'public', - 'fqdn', 'scheme', 'memory', - 'memory_overallocate', 'disk', - 'disk_overallocate', 'upload_size', + $repo->update($id, $request->intersect([ + 'name', 'location_id', 'public', 'fqdn', 'scheme', 'memory', + 'memory_overallocate', 'disk', 'disk_overallocate', 'upload_size', 'daemonSFTP', 'daemonListen', 'reset_secret', ])); - Alert::success('Successfully update this node\'s information. If you changed any daemon settings you will need to restart it now.')->flash(); - return redirect()->route('admin.nodes.view', [ - 'id' => $id, - 'tab' => 'tab_settings', - ]); - } catch (DisplayValidationException $e) { - return redirect()->route('admin.nodes.view', $id)->withErrors(json_decode($e->getMessage()))->withInput(); - } catch (DisplayException $e) { - Alert::danger($e->getMessage())->flash(); - } catch (\Exception $e) { - Log::error($e); + Alert::success('Successfully updated this node\'s information. If you changed any daemon settings you will need to restart it now.')->flash(); + } catch (DisplayValidationException $ex) { + return redirect()->route('admin.nodes.view.settings', $id)->withErrors(json_decode($ex->getMessage()))->withInput(); + } catch (DisplayException $ex) { + Alert::danger($ex->getMessage())->flash(); + } catch (\Exception $ex) { + Log::error($ex); Alert::danger('An unhandled exception occured while attempting to edit this node. Please try again.')->flash(); } - return redirect()->route('admin.nodes.view', [ - 'id' => $id, - 'tab' => 'tab_settings', - ])->withInput(); + return redirect()->route('admin.nodes.view.settings', $id)->withInput(); } - public function deallocateSingle(Request $request, $node, $allocation) + /** + * Removes a single allocation from a node. + * + * @param Request $request + * @param int $node + * @param int $allocation [description] + * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse + */ + public function allocationRemoveSingle(Request $request, $node, $allocation) { $query = Models\Allocation::where('node_id', $node)->whereNull('server_id')->where('id', $allocation)->delete(); - if ((int) $query === 0) { + if ($query < 1) { return response()->json([ 'error' => 'Unable to find an allocation matching those details to delete.', ], 400); @@ -161,33 +252,41 @@ class NodesController extends Controller return response('', 204); } - public function deallocateBlock(Request $request, $node) + /** + * Remove all allocations for a specific IP at once on a node. + * + * @param Request $request + * @param int $node + * @return \Illuminate\Http\RedirectResponse + */ + public function allocationRemoveBlock(Request $request, $node) { $query = Models\Allocation::where('node_id', $node)->whereNull('server_id')->where('ip', $request->input('ip'))->delete(); - if ((int) $query === 0) { + if ($query < 1) { Alert::danger('There was an error while attempting to delete allocations on that IP.')->flash(); - - return redirect()->route('admin.nodes.view', [ - 'id' => $node, - 'tab' => 'tab_allocations', - ]); + } else { + Alert::success('Deleted all unallocated ports for ' . $request->input('ip') . '.')->flash(); } - Alert::success('Deleted all unallocated ports for ' . $request->input('ip') . '.')->flash(); - return redirect()->route('admin.nodes.view', [ - 'id' => $node, - 'tab' => 'tab_allocation', - ]); + return redirect()->route('admin.nodes.view.allocation', $node); } - public function setAlias(Request $request, $node) + /** + * Sets an alias for a specific allocation on a node. + * + * @param Request $request + * @param int $node + * @return \Illuminate\Http\Response + * @throws \Exception + */ + public function allocationSetAlias(Request $request, $node) { - if (! $request->input('allocation')) { + if (! $request->input('allocation_id')) { return response('Missing required parameters.', 422); } try { - $update = Models\Allocation::findOrFail($request->input('allocation')); + $update = Models\Allocation::findOrFail($request->input('allocation_id')); $update->ip_alias = (empty($request->input('alias'))) ? null : $request->input('alias'); $update->save(); @@ -197,100 +296,74 @@ class NodesController extends Controller } } - public function getAllocationsJson(Request $request, $id) + /** + * Creates new allocations on a node. + * + * @param Request $request + * @param int $node + * @return \Illuminate\Http\RedirectResponse + */ + public function createAllocation(Request $request, $node) { - $allocations = Models\Allocation::select('ip')->where('node_id', $id)->groupBy('ip')->get(); - - return response()->json($allocations); - } - - public function postAllocations(Request $request, $id) - { - $validator = Validator::make($request->all(), [ - 'allocate_ip.*' => 'required|string', - 'allocate_port.*' => 'required', - ]); - - if ($validator->fails()) { - return redirect()->route('admin.nodes.view', [ - 'id' => $id, - 'tab' => 'tab_allocation', - ])->withErrors($validator->errors())->withInput(); - } - - $processedData = []; - foreach ($request->input('allocate_ip') as $ip) { - if (! array_key_exists($ip, $processedData)) { - $processedData[$ip] = []; - } - } - - foreach ($request->input('allocate_port') as $portid => $ports) { - if (array_key_exists($portid, $request->input('allocate_ip'))) { - $json = json_decode($ports); - if (json_last_error() === 0 && ! empty($json)) { - foreach ($json as &$parsed) { - array_push($processedData[$request->input('allocate_ip')[$portid]], $parsed->value); - } - } - } - } + $repo = new NodeRepository; try { - $node = new NodeRepository; - $node->addAllocations($id, $processedData); - Alert::success('Successfully added new allocations to this node.')->flash(); - } catch (DisplayException $e) { - Alert::danger($e->getMessage())->flash(); - } catch (\Exception $e) { - Log::error($e); - Alert::danger('An unhandled exception occured while attempting to add allocations this node. Please try again.')->flash(); - } finally { - return redirect()->route('admin.nodes.view', [ - 'id' => $id, - 'tab' => 'tab_allocation', - ]); + $repo->addAllocations($node, $request->intersect(['allocation_ip', 'allocation_alias', 'allocation_ports'])); + Alert::success('Successfully added new allocations!')->flash(); + } catch (DisplayValidationException $ex) { + return redirect()->route('admin.nodes.view.allocation', $node)->withErrors(json_decode($ex->getMessage()))->withInput(); + } catch (DisplayException $ex) { + Alert::danger($ex->getMessage())->flash(); + } catch (\Exception $ex) { + Log::error($ex); + Alert::danger('An unhandled exception occured while attempting to add allocations this node. This error has been logged.')->flash(); } + + return redirect()->route('admin.nodes.view.allocation', $node); } - public function deleteNode(Request $request, $id) + /** + * Deletes a node from the system. + * + * @param Request $request + * @param int $id + * @return \Illuminate\Http\RedirectResponse + */ + public function delete(Request $request, $id) { + $repo = new NodeRepository; + try { - $repo = new NodeRepository; $repo->delete($id); Alert::success('Successfully deleted the requested node from the panel.')->flash(); return redirect()->route('admin.nodes'); - } catch (DisplayException $e) { - Alert::danger($e->getMessage())->flash(); - } catch (\Exception $e) { - Log::error($e); + } catch (DisplayException $ex) { + Alert::danger($ex->getMessage())->flash(); + } catch (\Exception $ex) { + Log::error($ex); Alert::danger('An unhandled exception occured while attempting to delete this node. Please try again.')->flash(); } - return redirect()->route('admin.nodes.view', [ - 'id' => $id, - 'tab' => 'tab_delete', - ]); + return redirect()->route('admin.nodes.view', $id); } - public function getConfigurationToken(Request $request, $id) + /** + * Returns the configuration token to auto-deploy a node. + * + * @param Request $request + * @param int $id + * @return \Illuminate\Http\JsonResponse + */ + public function setToken(Request $request, $id) { - // Check if Node exists. Will lead to 404 if not. - Models\Node::findOrFail($id); + $node = Models\Node::findOrFail($id); - // Create a token - $token = new Models\NodeConfigurationToken(); - $token->node = $id; - $token->token = str_random(32); - $token->expires_at = Carbon::now()->addMinutes(5); // Expire in 5 Minutes - $token->save(); + $t = Models\NodeConfigurationToken::create([ + 'node_id' => $id, + 'token' => str_random(32), + ]); - $token_response = [ - 'token' => $token->token, - 'expires_at' => $token->expires_at->toDateTimeString(), - ]; - - return response()->json($token_response, 200); + return response()->json(['token' => $t->token]); } } diff --git a/app/Http/Controllers/Admin/ServersController.php b/app/Http/Controllers/Admin/ServersController.php index 017dc1fcc..6b1808516 100644 --- a/app/Http/Controllers/Admin/ServersController.php +++ b/app/Http/Controllers/Admin/ServersController.php @@ -26,8 +26,10 @@ namespace Pterodactyl\Http\Controllers\Admin; use Log; use Alert; +use Javascript; use Pterodactyl\Models; use Illuminate\Http\Request; +use GuzzleHttp\Exception\TransferException; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Repositories\ServerRepository; @@ -37,419 +39,540 @@ use Pterodactyl\Exceptions\DisplayValidationException; class ServersController extends Controller { /** - * Controller Constructor. + * Display the index page with all servers currently on the system. + * + * @param Request $request + * @return \Illuminate\View\View */ - public function __construct() + public function index(Request $request) { - // - } + $servers = Models\Server::withTrashed()->with( + 'node', 'user', 'allocation' + ); + + if (! is_null($request->input('query'))) { + $servers->search($request->input('query')); + } - public function getIndex(Request $request) - { return view('admin.servers.index', [ - 'servers' => Models\Server::withTrashed()->with('node', 'user')->paginate(25), + 'servers' => $servers->paginate(25), ]); } - public function getNew(Request $request) + /** + * Display create new server page. + * + * @param Request $request + * @return \Illuminate\View\View + */ + public function new(Request $request) { + $services = Models\Service::with('options.packs', 'options.variables')->get(); + Javascript::put([ + 'services' => $services->map(function ($item) { + return array_merge($item->toArray(), [ + 'options' => $item->options->keyBy('id')->toArray(), + ]); + })->keyBy('id'), + ]); + return view('admin.servers.new', [ 'locations' => Models\Location::all(), - 'services' => Models\Service::all(), + 'services' => $services, ]); } - public function getView(Request $request, $id) + /** + * Create server controller method. + * + * @param Request $request + * @return \Illuminate\Response\RedirectResponse + */ + public function create(Request $request) { - $server = Models\Server::withTrashed()->with( - 'user', 'option.variables', 'variables', - 'node.allocations', 'databases.host' - )->findOrFail($id); + try { + $repo = new ServerRepository; + $server = $repo->create($request->except('_token')); + return redirect()->route('admin.servers.view', $server->id); + } catch (DisplayValidationException $ex) { + return redirect()->route('admin.servers.new')->withErrors(json_decode($ex->getMessage()))->withInput(); + } catch (DisplayException $ex) { + Alert::danger($ex->getMessage())->flash(); + } catch (\Exception $ex) { + Log::error($ex); + Alert::danger('An unhandled exception occured while attemping to add this server. Please try again.')->flash(); + } + + return redirect()->route('admin.servers.new')->withInput(); + } + + /** + * Returns a tree of all avaliable nodes in a given location. + * + * @param Request $request + * @return array + */ + public function newServerNodes(Request $request) + { + $nodes = Models\Node::with('allocations')->where('location_id', $request->input('location'))->get(); + + return $nodes->map(function ($item) { + $filtered = $item->allocations->where('server_id', null)->map(function ($map) { + return collect($map)->only(['id', 'ip', 'port']); + }); + + $item->ports = $filtered->map(function ($map) use ($item) { + return [ + 'id' => $map['id'], + 'text' => $map['ip'] . ':' . $map['port'], + ]; + })->values(); + + return [ + 'id' => $item->id, + 'text' => $item->name, + 'allocations' => $item->ports, + ]; + })->values(); + } + + /** + * Display the index when viewing a specific server. + * + * @param Request $request + * @param int $id + * @return \Illuminate\View\View + */ + public function viewIndex(Request $request, $id) + { + return view('admin.servers.view.index', ['server' => Models\Server::withTrashed()->findOrFail($id)]); + } + + /** + * Display the details page when viewing a specific server. + * + * @param Request $request + * @param int $id + * @return \Illuminate\View\View + */ + public function viewDetails(Request $request, $id) + { + $server = Models\Server::where('installed', 1)->findOrFail($id); + + return view('admin.servers.view.details', ['server' => $server]); + } + + /** + * Display the build details page when viewing a specific server. + * + * @param Request $request + * @param int $id + * @return \Illuminate\View\View + */ + public function viewBuild(Request $request, $id) + { + $server = Models\Server::where('installed', 1)->with('node.allocations')->findOrFail($id); + + return view('admin.servers.view.build', [ + 'server' => $server, + 'assigned' => $server->node->allocations->where('server_id', $server->id)->sortBy('port')->sortBy('ip'), + 'unassigned' => $server->node->allocations->where('server_id', null)->sortBy('port')->sortBy('ip'), + ]); + } + + /** + * Display startup configuration page for a server. + * + * @param Request $request + * @param int $id + * @return \Illuminate\View\View + */ + public function viewStartup(Request $request, $id) + { + $server = Models\Server::where('installed', 1)->with('option.variables', 'variables')->findOrFail($id); $server->option->variables->transform(function ($item, $key) use ($server) { $item->server_value = $server->variables->where('variable_id', $item->id)->pluck('variable_value')->first(); return $item; }); - return view('admin.servers.view', [ + return view('admin.servers.view.startup', ['server' => $server]); + } + + /** + * Display the database management page for a specific server. + * + * @param Request $request + * @param int $id + * @return \Illuminate\View\View + */ + public function viewDatabase(Request $request, $id) + { + $server = Models\Server::where('installed', 1)->with('databases.host')->findOrFail($id); + + return view('admin.servers.view.database', [ + 'hosts' => Models\DatabaseServer::all(), 'server' => $server, - 'assigned' => $server->node->allocations->where('server_id', $server->id)->sortBy('port')->sortBy('ip'), - 'unassigned' => $server->node->allocations->where('server_id', null)->sortBy('port')->sortBy('ip'), - 'db_servers' => Models\DatabaseServer::all(), ]); } - public function postNewServer(Request $request) + /** + * Display the management page when viewing a specific server. + * + * @param Request $request + * @param int $id + * @return \Illuminate\View\View + */ + public function viewManage(Request $request, $id) { + return view('admin.servers.view.manage', ['server' => Models\Server::findOrFail($id)]); + } + + /** + * Display the deletion page for a server. + * + * @param Request $request + * @param int $id + * @return \Illuminate\View\View + */ + public function viewDelete(Request $request, $id) + { + return view('admin.servers.view.delete', ['server' => Models\Server::withTrashed()->findOrFail($id)]); + } + + /** + * Update the details for a server. + * + * @param Request $request + * @param int $id + * @return \Illuminate\Response\RedirectResponse + */ + public function setDetails(Request $request, $id) + { + $repo = new ServerRepository; try { - $server = new ServerRepository; - $response = $server->create($request->except('_token')); - - return redirect()->route('admin.servers.view', ['id' => $response->id]); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.servers.new')->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - - return redirect()->route('admin.servers.new')->withInput(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occured while attemping to add this server. Please try again.')->flash(); - - return redirect()->route('admin.servers.new')->withInput(); - } - } - - /** - * Returns a JSON tree of all avaliable nodes in a given location. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Contracts\View\View - */ - public function postNewServerGetNodes(Request $request) - { - if (! $request->input('location')) { - return response()->json([ - 'error' => 'Missing location in request.', - ], 500); - } - - return response()->json(Models\Node::select('id', 'name', 'public')->where('location_id', $request->input('location'))->get()); - } - - /** - * Returns a JSON tree of all avaliable IPs and Ports on a given node. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Contracts\View\View - */ - public function postNewServerGetIps(Request $request) - { - if (! $request->input('node')) { - return response()->json([ - 'error' => 'Missing node in request.', - ], 500); - } - - $ips = Models\Allocation::where('node_id', $request->input('node'))->whereNull('server_id')->get(); - $listing = []; - - foreach ($ips as &$ip) { - if (array_key_exists($ip->ip, $listing)) { - $listing[$ip->ip] = array_merge($listing[$ip->ip], [$ip->port]); - } else { - $listing[$ip->ip] = [$ip->port]; - } - } - - return response()->json($listing); - } - - /** - * Returns a JSON tree of all avaliable options for a given service. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Contracts\View\View - */ - public function postNewServerServiceOption(Request $request) - { - if (! $request->input('service')) { - return response()->json([ - 'error' => 'Missing service in request.', - ], 500); - } - - $service = Models\Service::select('executable', 'startup')->where('id', $request->input('service'))->first(); - - return response()->json(Models\ServiceOption::select('id', 'name', 'docker_image')->where('service_id', $request->input('service'))->orderBy('name', 'asc')->get()); - } - - /** - * Returns a JSON tree of all avaliable variables for a given service option. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Contracts\View\View - */ - public function postNewServerOptionDetails(Request $request) - { - if (! $request->input('option')) { - return response()->json([ - 'error' => 'Missing option in request.', - ], 500); - } - - $option = Models\ServiceOption::with('variables')->with(['packs' => function ($query) { - $query->where('selectable', true); - }])->findOrFail($request->input('option')); - - return response()->json([ - 'packs' => $option->packs, - 'variables' => $option->variables, - 'exec' => $option->display_executable, - 'startup' => $option->display_startup, - ]); - } - - public function postUpdateServerDetails(Request $request, $id) - { - try { - $server = new ServerRepository; - $server->updateDetails($id, [ - 'owner' => $request->input('owner'), - 'name' => $request->input('name'), - 'reset_token' => ($request->input('reset_token', false) === 'on') ? true : false, - ]); + $repo->updateDetails($id, $request->intersect([ + 'owner_id', 'name', 'reset_token', + ])); Alert::success('Server details were successfully updated.')->flash(); } catch (DisplayValidationException $ex) { - return redirect()->route('admin.servers.view', [ - 'id' => $id, - 'tab' => 'tab_details', - ])->withErrors(json_decode($ex->getMessage()))->withInput(); + return redirect()->route('admin.servers.view.details', $id)->withErrors(json_decode($ex->getMessage()))->withInput(); } catch (DisplayException $ex) { Alert::danger($ex->getMessage())->flash(); } catch (\Exception $ex) { Log::error($ex); - Alert::danger('An unhandled exception occured while attemping to update this server. Please try again.')->flash(); + Alert::danger('An unhandled exception occured while attemping to update this server. This error has been logged.')->flash(); } - return redirect()->route('admin.servers.view', [ - 'id' => $id, - 'tab' => 'tab_details', - ])->withInput(); + return redirect()->route('admin.servers.view.details', $id)->withInput(); } - public function postUpdateContainerDetails(Request $request, $id) + /** + * Set the new docker container for a server. + * + * @param Request $request + * @param int $id + * @return \Illuminate\Response\RedirectResponse + */ + public function setContainer(Request $request, $id) { + $repo = new ServerRepository; + try { - $server = new ServerRepository; - $server->updateContainer($id, ['image' => $request->input('docker_image')]); + $repo->updateContainer($id, $request->intersect('docker_image')); + Alert::success('Successfully updated this server\'s docker image.')->flash(); } catch (DisplayValidationException $ex) { - return redirect()->route('admin.servers.view', [ - 'id' => $id, - 'tab' => 'tab_details', - ])->withErrors(json_decode($ex->getMessage()))->withInput(); + return redirect()->route('admin.servers.view.details', $id)->withErrors(json_decode($ex->getMessage()))->withInput(); } catch (DisplayException $ex) { Alert::danger($ex->getMessage())->flash(); } catch (\Exception $ex) { Log::error($ex); - Alert::danger('An unhandled exception occured while attemping to update this server\'s docker image. Please try again.')->flash(); + Alert::danger('An unhandled exception occured while attemping to update this server\'s docker image. This error has been logged.')->flash(); } - return redirect()->route('admin.servers.view', [ - 'id' => $id, - 'tab' => 'tab_details', - ]); + return redirect()->route('admin.servers.view.details', $id); } - public function postUpdateServerToggleBuild(Request $request, $id) + /** + * Toggles the install status for a server. + * + * @param Request $request + * @param int $id + * @return \Illuminate\Response\RedirectResponse + */ + public function toggleInstall(Request $request, $id) + { + $repo = new ServerRepository; + try { + $repo->toggleInstall($id); + + Alert::success('Server install status was successfully toggled.')->flash(); + } catch (DisplayException $ex) { + Alert::danger($ex->getMessage())->flash(); + } catch (\Exception $ex) { + Log::error($ex); + Alert::danger('An unhandled exception occured while attemping to toggle this servers status. This error has been logged.')->flash(); + } + + return redirect()->route('admin.servers.view.manage', $id); + } + + /** + * Setup a server to have a container rebuild. + * + * @param Request $request + * @param int $id + * @return \Illuminate\Response\RedirectResponse + */ + public function rebuildContainer(Request $request, $id) { $server = Models\Server::with('node')->findOrFail($id); try { - $res = $server->node->guzzleClient([ + $server->node->guzzleClient([ 'X-Access-Server' => $server->uuid, 'X-Access-Token' => $server->node->daemonSecret, ])->request('POST', '/server/rebuild'); + Alert::success('A rebuild has been queued successfully. It will run the next time this server is booted.')->flash(); - } catch (\GuzzleHttp\Exception\TransferException $ex) { + } catch (TransferException $ex) { Log::warning($ex); - Alert::danger('An error occured while attempting to toggle a rebuild.')->flash(); + Alert::danger('A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.')->flash(); } - return redirect()->route('admin.servers.view', [ - 'id' => $id, - 'tab' => 'tab_manage', - ]); + return redirect()->route('admin.servers.view.manage', $id); } - public function postUpdateServerUpdateBuild(Request $request, $id) + /** + * Manage the suspension status for a server. + * + * @param Request $request + * @param int $id + * @return \Illuminate\Response\RedirectResponse + */ + public function manageSuspension(Request $request, $id) { + $repo = new ServerRepository; + $action = $request->input('action'); + + if (! in_array($action, ['suspend', 'unsuspend'])) { + Alert::danger('Invalid action was passed to function.')->flash(); + + return redirect()->route('admin.servers.view.manage', $id); + } + try { - $server = new ServerRepository; - $server->changeBuild($id, $request->only([ - 'default', 'add_additional', - 'remove_additional', 'memory', - 'swap', 'io', 'cpu', - ])); - Alert::success('Server details were successfully updated.')->flash(); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.servers.view', [ - 'id' => $id, - 'tab' => 'tab_build', - ])->withErrors(json_decode($ex->getMessage()))->withInput(); + $repo->$action($id); + + Alert::success('Server has been ' . $action . 'ed.'); } catch (DisplayException $ex) { Alert::danger($ex->getMessage())->flash(); - - return redirect()->route('admin.servers.view', [ - 'id' => $id, - 'tab' => 'tab_build', - ]); } catch (\Exception $ex) { Log::error($ex); - Alert::danger('An unhandled exception occured while attemping to add this server. Please try again.')->flash(); + Alert::danger('An unhandled exception occured while attemping to ' . $action . ' this server. This error has been logged.')->flash(); } - return redirect()->route('admin.servers.view', [ - 'id' => $id, - 'tab' => 'tab_build', - ]); + return redirect()->route('admin.servers.view.manage', $id); } - public function deleteServer(Request $request, $id, $force = null) + /** + * Update the build configuration for a server. + * + * @param Request $request + * @param int $id + * @return \Illuminate\Response\RedirectResponse + */ + public function updateBuild(Request $request, $id) { + $repo = new ServerRepository; + try { - $server = new ServerRepository; - $server->deleteServer($id, $force); + $repo->changeBuild($id, $request->intersect([ + 'allocation_id', 'add_allocations', 'remove_allocations', + 'memory', 'swap', 'io', 'cpu', + ])); + + Alert::success('Server details were successfully updated.')->flash(); + } catch (DisplayValidationException $ex) { + return redirect()->route('admin.servers.view.build', $id)->withErrors(json_decode($ex->getMessage()))->withInput(); + } catch (DisplayException $ex) { + Alert::danger($ex->getMessage())->flash(); + } catch (\Exception $ex) { + Log::error($ex); + Alert::danger('An unhandled exception occured while attemping to add this server. This error has been logged.')->flash(); + } + + return redirect()->route('admin.servers.view.build', $id); + } + + /** + * Start the server deletion process. + * + * @param Request $request + * @param int $id + * @return \Illuminate\Response\RedirectResponse + */ + public function delete(Request $request, $id) + { + $repo = new ServerRepository; + + try { + $repo->queueDeletion($id, ($request->input('is_force') > 0)); Alert::success('Server has been marked for deletion on the system.')->flash(); + } catch (DisplayException $ex) { + Alert::danger($ex->getMessage())->flash(); + } catch (\Exception $ex) { + Log::error($ex); + Alert::danger('An unhandled exception occured while attemping to delete this server. This error has been logged.')->flash(); + } + + return redirect()->route('admin.servers.view.delete', $id); + } + + /** + * Cancels a pending server deletion request. + * + * @param Request $request + * @param int $id + * @return \Illuminate\Response\RedirectResponse + */ + public function cancelDeletion(Request $request, $id) + { + $repo = new ServerRepository; + + $repo->cancelDeletion($id); + Alert::success('Server deletion has been cancelled. This server will remain suspended until you unsuspend it.')->flash(); + + return redirect()->route('admin.servers.view.delete', $id); + } + + /** + * Skips the queue and continues the server deletion process. + * + * @param Request $request + * @param int $id + * @return \Illuminate\Response\RedirectResponse + */ + public function continueDeletion(Request $request, $id, $method) + { + $repo = new ServerRepository; + + try { + $repo->delete($id, (isset($method) && $method === 'force')); + Alert::success('Server was successfully deleted from the system.')->flash(); return redirect()->route('admin.servers'); } catch (DisplayException $ex) { Alert::danger($ex->getMessage())->flash(); + } catch (TransferException $ex) { + Log::warning($ex); + Alert::danger('A TransferException occurred while attempting to delete this server from the daemon, please ensure it is running. This error has been logged.')->flash(); } catch (\Exception $ex) { Log::error($ex); - Alert::danger('An unhandled exception occured while attemping to delete this server. Please try again.')->flash(); + Alert::danger('An unhandled exception occured while attemping to delete this server. This error has been logged.')->flash(); } - return redirect()->route('admin.servers.view', [ - 'id' => $id, - 'tab' => 'tab_delete', - ]); + return redirect()->route('admin.servers.view.delete', $id); } - public function postToggleInstall(Request $request, $id) + /** + * Update the startup command as well as variables. + * + * @param Request $request + * @param int $id + * @return \Illuminate\Response\RedirectResponse + */ + public function saveStartup(Request $request, $id) { + $repo = new ServerRepository; + try { - $server = new ServerRepository; - $server->toggleInstall($id); - Alert::success('Server status was successfully toggled.')->flash(); + $repo->updateStartup($id, $request->except('_token'), true); + + Alert::success('Startup variables were successfully modified and assigned for this server.')->flash(); } catch (DisplayException $ex) { Alert::danger($ex->getMessage())->flash(); + } catch (TransferException $ex) { + Log::warning($ex); + Alert::danger('A TransferException occurred while attempting to update the startup for this server, please ensure the daemon is running. This error has been logged.')->flash(); } catch (\Exception $ex) { Log::error($ex); - Alert::danger('An unhandled exception occured while attemping to toggle this servers status.')->flash(); - } finally { - return redirect()->route('admin.servers.view', [ - 'id' => $id, - 'tab' => 'tab_manage', - ]); + Alert::danger('An unhandled exception occured while attemping to update startup variables for this server. This error has been logged.')->flash(); } + + return redirect()->route('admin.servers.view.startup', $id); } - public function postUpdateServerStartup(Request $request, $id) + /** + * Creates a new database assigned to a specific server. + * @param Request $request + * @param int $id + * @return \Illuminate\Response\RedirectResponse + */ + public function newDatabase(Request $request, $id) { - try { - $server = new ServerRepository; - $server->updateStartup($id, $request->except([ - '_token', - ]), true); - Alert::success('Server startup variables were successfully updated.')->flash(); - } catch (\Pterodactyl\Exceptions\DisplayException $e) { - Alert::danger($e->getMessage())->flash(); - } catch (\Exception $e) { - Log::error($e); - Alert::danger('An unhandled exception occured while attemping to update startup variables for this server. Please try again.')->flash(); - } finally { - return redirect()->route('admin.servers.view', [ - 'id' => $id, - 'tab' => 'tab_startup', - ])->withInput(); - } - } + $repo = new DatabaseRepository; - public function postDatabase(Request $request, $id) - { try { - $repo = new DatabaseRepository; - $repo->create($id, $request->only([ - 'db_server', 'database', 'remote', - ])); - Alert::success('Added new database to this server.')->flash(); + $repo->create($id, $request->only(['host', 'database', 'connection'])); + + Alert::success('A new database was assigned to this server successfully.')->flash(); } catch (DisplayValidationException $ex) { - return redirect()->route('admin.servers.view', [ - 'id' => $id, - 'tab' => 'tab_database', - ])->withInput()->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An exception occured while attempting to add a new database for this server.')->flash(); - } - - return redirect()->route('admin.servers.view', [ - 'id' => $id, - 'tab' => 'tab_database', - ])->withInput(); - } - - public function postSuspendServer(Request $request, $id) - { - try { - $repo = new ServerRepository; - $repo->suspend($id); - Alert::success('Server has been suspended on the system. All running processes have been stopped and will not be startable until it is un-suspended.'); - } catch (DisplayException $e) { - Alert::danger($e->getMessage())->flash(); - } catch (\Exception $e) { - Log::error($e); - Alert::danger('An unhandled exception occured while attemping to suspend this server. Please try again.')->flash(); - } finally { - return redirect()->route('admin.servers.view', [ - 'id' => $id, - 'tab' => 'tab_manage', - ]); - } - } - - public function postUnsuspendServer(Request $request, $id) - { - try { - $repo = new ServerRepository; - $repo->unsuspend($id); - Alert::success('Server has been unsuspended on the system. Access has been re-enabled.'); - } catch (DisplayException $e) { - Alert::danger($e->getMessage())->flash(); - } catch (\Exception $e) { - Log::error($e); - Alert::danger('An unhandled exception occured while attemping to unsuspend this server. Please try again.')->flash(); - } finally { - return redirect()->route('admin.servers.view', [ - 'id' => $id, - 'tab' => 'tab_manage', - ]); - } - } - - public function postQueuedDeletionHandler(Request $request, $id) - { - try { - $repo = new ServerRepository; - if (! is_null($request->input('cancel'))) { - $repo->cancelDeletion($id); - Alert::success('Server deletion has been cancelled. This server will remain suspended until you unsuspend it.')->flash(); - - return redirect()->route('admin.servers.view', $id); - } elseif (! is_null($request->input('delete'))) { - $repo->deleteNow($id); - Alert::success('Server was successfully deleted from the system.')->flash(); - - return redirect()->route('admin.servers'); - } elseif (! is_null($request->input('force_delete'))) { - $repo->deleteNow($id, true); - Alert::success('Server was successfully force deleted from the system.')->flash(); - - return redirect()->route('admin.servers'); - } + return redirect()->route('admin.servers.view.database', $id)->withInput()->withErrors(json_decode($ex->getMessage()))->withInput(); } catch (DisplayException $ex) { Alert::danger($ex->getMessage())->flash(); - - return redirect()->route('admin.servers.view', $id); } catch (\Exception $ex) { Log::error($ex); - Alert::danger('An unhandled error occured while attempting to perform this action.')->flash(); + Alert::danger('An exception occured while attempting to add a new database for this server. This error has been logged.')->flash(); + } - return redirect()->route('admin.servers.view', $id); + return redirect()->route('admin.servers.view.database', $id)->withInput(); + } + + /** + * Resets the database password for a specific database on this server. + * @param Request $request + * @param int $id + * @return \Illuminate\Response\RedirectResponse + */ + public function resetDatabasePassword(Request $request, $id) + { + $database = Models\Database::where('server_id', $id)->findOrFail($request->input('database')); + $repo = new DatabaseRepository; + + try { + $repo->password($database->id, str_random(20)); + + return response('', 204); + } catch (\Exception $ex) { + Log::error($ex); + + return response()->json(['error' => 'A unhandled exception occurred while attempting to reset this password. This error has been logged.'], 503); + } + } + + /** + * Deletes a database from a server. + * @param Request $request + * @param int $id + * @return \Illuminate\Response\RedirectResponse + */ + public function deleteDatabase(Request $request, $id, $database) + { + $database = Models\Database::where('server_id', $id)->findOrFail($database); + $repo = new DatabaseRepository; + + try { + $repo->drop($database->id); + + return response('', 204); + } catch (\Exception $ex) { + Log::error($ex); + + return response()->json(['error' => 'A unhandled exception occurred while attempting to drop this database. This error has been logged.'], 503); } } } diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 8f90795c2..0de02eca9 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -47,8 +47,14 @@ class UserController extends Controller // @TODO: implement nicolaslopezj/searchable to clean up this disaster. public function getIndex(Request $request) { + $users = User::withCount('servers'); + + if (! is_null($request->input('query'))) { + $users->search($request->input('query')); + } + return view('admin.users.index', [ - 'users' => User::paginate(25), + 'users' => $users->paginate(25), ]); } @@ -124,6 +130,12 @@ class UserController extends Controller public function getJson(Request $request) { - return User::select('email')->get()->pluck('email'); + return User::select('id', 'email', 'username', 'name_first', 'name_last') + ->search($request->input('q')) + ->get()->transform(function ($item) { + $item->md5 = md5(strtolower($item->email)); + + return $item; + }); } } diff --git a/app/Http/Controllers/Remote/RemoteController.php b/app/Http/Controllers/Remote/RemoteController.php index 23ae805b6..2e8b782a1 100644 --- a/app/Http/Controllers/Remote/RemoteController.php +++ b/app/Http/Controllers/Remote/RemoteController.php @@ -105,27 +105,26 @@ class RemoteController extends Controller return response('', 201); } - public function getConfiguration(Request $request, $tokenString) + public function getConfiguration(Request $request, $token) { // Try to query the token and the node from the database try { - $token = Models\NodeConfigurationToken::where('token', $tokenString)->firstOrFail(); - $node = Models\Node::findOrFail($token->node); + $model = Models\NodeConfigurationToken::with('node')->where('token', $token)->firstOrFail(); } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) { return response()->json(['error' => 'token_invalid'], 403); } // Check if token is expired - if ($token->expires_at->lt(Carbon::now())) { - $token->delete(); + if ($model->created_at->lt(Carbon::now())) { + $model->delete(); return response()->json(['error' => 'token_expired'], 403); } // Delete the token, it's one-time use - $token->delete(); + $model->delete(); // Manually as getConfigurationAsJson() returns it in correct format already - return response($node->getConfigurationAsJson())->header('Content-Type', 'text/json'); + return response($model->node->getConfigurationAsJson())->header('Content-Type', 'text/json'); } } diff --git a/app/Http/Controllers/Server/AjaxController.php b/app/Http/Controllers/Server/AjaxController.php index 8f3736da7..4609ae270 100644 --- a/app/Http/Controllers/Server/AjaxController.php +++ b/app/Http/Controllers/Server/AjaxController.php @@ -224,17 +224,16 @@ class AjaxController extends Controller $server = Models\Server::byUuid($uuid); $this->authorize('reset-db-password', $server); - $database = Models\Database::where('id', $request->input('database'))->where('server_id', $server->id)->firstOrFail(); + $database = Models\Database::where('server_id', $server->id)->findOrFail($request->input('database')); + $repo = new Repositories\DatabaseRepository; + try { - $repo = new Repositories\DatabaseRepository; - $password = str_random(16); - $repo->modifyPassword($request->input('database'), $password); + $password = str_random(20); + $repo->password($database->id, $password); return response($password); - } catch (\Pterodactyl\Exceptions\DisplayException $ex) { - return response()->json([ - 'error' => $ex->getMessage(), - ], 503); + } catch (DisplayException $ex) { + return response()->json(['error' => $ex->getMessage()], 503); } catch (\Exception $ex) { Log::error($ex); diff --git a/app/Http/Controllers/Server/ServerController.php b/app/Http/Controllers/Server/ServerController.php index 07dee5439..7be93ff61 100644 --- a/app/Http/Controllers/Server/ServerController.php +++ b/app/Http/Controllers/Server/ServerController.php @@ -309,9 +309,7 @@ class ServerController extends Controller try { $repo = new ServerRepository; - $repo->updateStartup($server->id, $request->except([ - '_token', - ])); + $repo->updateStartup($server->id, $request->except('_token')); Alert::success('Server startup variables were successfully updated.')->flash(); } catch (DisplayException $ex) { Alert::danger($ex->getMessage())->flash(); diff --git a/app/Http/Middleware/AdminAuthenticate.php b/app/Http/Middleware/AdminAuthenticate.php index c0d74f262..d533fc49d 100644 --- a/app/Http/Middleware/AdminAuthenticate.php +++ b/app/Http/Middleware/AdminAuthenticate.php @@ -24,7 +24,6 @@ namespace Pterodactyl\Http\Middleware; -use Theme; use Closure; use Illuminate\Contracts\Auth\Guard; @@ -69,9 +68,6 @@ class AdminAuthenticate return abort(403); } - // @TODO: eventually update admin themes - Theme::set('default'); - return $next($request); } } diff --git a/app/Http/Routes/AdminRoutes.php b/app/Http/Routes/AdminRoutes.php index 127055daa..c25cb91e3 100644 --- a/app/Http/Routes/AdminRoutes.php +++ b/app/Http/Routes/AdminRoutes.php @@ -121,99 +121,118 @@ class AdminRoutes // View All Servers $router->get('/', [ 'as' => 'admin.servers', - 'uses' => 'Admin\ServersController@getIndex', ]); + 'uses' => 'Admin\ServersController@index', + ]); // View Create Server Page $router->get('/new', [ 'as' => 'admin.servers.new', - 'uses' => 'Admin\ServersController@getNew', + 'uses' => 'Admin\ServersController@new', ]); // Handle POST Request for Creating Server $router->post('/new', [ - 'uses' => 'Admin\ServersController@postNewServer', + 'uses' => 'Admin\ServersController@create', ]); // Assorted Page Helpers - $router->post('/new/get-nodes', [ - 'uses' => 'Admin\ServersController@postNewServerGetNodes', + $router->post('/new/nodes', [ + 'as' => 'admin.servers.new.nodes', + 'uses' => 'Admin\ServersController@newServerNodes', ]); - $router->post('/new/get-ips', [ - 'uses' => 'Admin\ServersController@postNewServerGetIps', - ]); - - $router->post('/new/service-options', [ - 'uses' => 'Admin\ServersController@postNewServerServiceOption', - ]); - - $router->post('/new/option-details', [ - 'uses' => 'Admin\ServersController@postNewServerOptionDetails', - ]); - // End Assorted Page Helpers - - // View Specific Server $router->get('/view/{id}', [ 'as' => 'admin.servers.view', - 'uses' => 'Admin\ServersController@getView', + 'uses' => 'Admin\ServersController@viewIndex', ]); - // Database Stuffs - $router->post('/view/{id}/database', [ - 'as' => 'admin.servers.database', - 'uses' => 'Admin\ServersController@postDatabase', + $router->get('/view/{id}/details', [ + 'as' => 'admin.servers.view.details', + 'uses' => 'Admin\ServersController@viewDetails', ]); - // Change Server Details $router->post('/view/{id}/details', [ - 'uses' => 'Admin\ServersController@postUpdateServerDetails', + 'uses' => 'Admin\ServersController@setDetails', ]); - // Change Server Details - $router->post('/view/{id}/container', [ - 'as' => 'admin.servers.post.container', - 'uses' => 'Admin\ServersController@postUpdateContainerDetails', + $router->post('/view/{id}/details/container', [ + 'as' => 'admin.servers.view.details.container', + 'uses' => 'Admin\ServersController@setContainer', ]); - // Change Server Details - $router->post('/view/{id}/startup', [ - 'as' => 'admin.servers.post.startup', - 'uses' => 'Admin\ServersController@postUpdateServerStartup', + $router->get('/view/{id}/build', [ + 'as' => 'admin.servers.view.build', + 'uses' => 'Admin\ServersController@viewBuild', ]); - // Rebuild Server - $router->post('/view/{id}/rebuild', [ - 'uses' => 'Admin\ServersController@postUpdateServerToggleBuild', - ]); - - // Change Build Details $router->post('/view/{id}/build', [ - 'uses' => 'Admin\ServersController@postUpdateServerUpdateBuild', + 'uses' => 'Admin\ServersController@updateBuild', ]); - // Suspend Server - $router->post('/view/{id}/suspend', [ - 'uses' => 'Admin\ServersController@postSuspendServer', + $router->get('/view/{id}/startup', [ + 'as' => 'admin.servers.view.startup', + 'uses' => 'Admin\ServersController@viewStartup', ]); - // Unsuspend Server - $router->post('/view/{id}/unsuspend', [ - 'uses' => 'Admin\ServersController@postUnsuspendServer', + $router->post('/view/{id}/startup', [ + 'uses' => 'Admin\ServersController@saveStartup', ]); - // Change Install Status - $router->post('/view/{id}/installed', [ - 'uses' => 'Admin\ServersController@postToggleInstall', + $router->get('/view/{id}/database', [ + 'as' => 'admin.servers.view.database', + 'uses' => 'Admin\ServersController@viewDatabase', ]); - // Delete [force delete] - $router->delete('/view/{id}/{force?}', [ - 'uses' => 'Admin\ServersController@deleteServer', + $router->post('/view/{id}/database', [ + 'uses' => 'Admin\ServersController@newDatabase', ]); - $router->post('/view/{id}/queuedDeletion', [ - 'uses' => 'Admin\ServersController@postQueuedDeletionHandler', - 'as' => 'admin.servers.post.queuedDeletion', + $router->patch('/view/{id}/database', [ + 'uses' => 'Admin\ServersController@resetDatabasePassword', + ]); + + $router->delete('/view/{id}/database/{database}/delete', [ + 'as' => 'admin.servers.view.database.delete', + 'uses' => 'Admin\ServersController@deleteDatabase', + ]); + + $router->get('/view/{id}/manage', [ + 'as' => 'admin.servers.view.manage', + 'uses' => 'Admin\ServersController@viewManage', + ]); + + $router->post('/view/{id}/manage/toggle', [ + 'as' => 'admin.servers.view.manage.toggle', + 'uses' => 'Admin\ServersController@toggleInstall', + ]); + + $router->post('/view/{id}/manage/rebuild', [ + 'as' => 'admin.servers.view.manage.rebuild', + 'uses' => 'Admin\ServersController@rebuildContainer', + ]); + + $router->post('/view/{id}/manage/suspension', [ + 'as' => 'admin.servers.view.manage.suspension', + 'uses' => 'Admin\ServersController@manageSuspension', + ]); + + $router->get('/view/{id}/delete', [ + 'as' => 'admin.servers.view.delete', + 'uses' => 'Admin\ServersController@viewDelete', + ]); + + $router->post('/view/{id}/delete', [ + 'uses' => 'Admin\ServersController@delete', + ]); + + $router->post('/view/{id}/delete/continue/{force?}', [ + 'as' => 'admin.servers.view.delete.continue', + 'uses' => 'Admin\ServersController@continueDeletion', + ]); + + $router->post('/view/{id}/delete/cancel', [ + 'as' => 'admin.servers.view.delete.cancel', + 'uses' => 'Admin\ServersController@cancelDeletion', ]); }); @@ -230,66 +249,75 @@ class AdminRoutes // View All Nodes $router->get('/', [ 'as' => 'admin.nodes', - 'uses' => 'Admin\NodesController@getIndex', + 'uses' => 'Admin\NodesController@index', ]); // Add New Node $router->get('/new', [ 'as' => 'admin.nodes.new', - 'uses' => 'Admin\NodesController@getNew', + 'uses' => 'Admin\NodesController@new', ]); $router->post('/new', [ - 'uses' => 'Admin\NodesController@postNew', + 'uses' => 'Admin\NodesController@create', ]); - // View Node $router->get('/view/{id}', [ 'as' => 'admin.nodes.view', - 'uses' => 'Admin\NodesController@getView', + 'uses' => 'Admin\NodesController@viewIndex', ]); - $router->post('/view/{id}', [ - 'uses' => 'Admin\NodesController@postView', + $router->get('/view/{id}/settings', [ + 'as' => 'admin.nodes.view.settings', + 'uses' => 'Admin\NodesController@viewSettings', ]); - $router->delete('/view/{id}/deallocate/single/{allocation}', [ - 'uses' => 'Admin\NodesController@deallocateSingle', + $router->post('/view/{id}/settings', [ + 'uses' => 'Admin\NodesController@updateSettings', ]); - $router->post('/view/{id}/deallocate/block', [ - 'uses' => 'Admin\NodesController@deallocateBlock', + $router->get('/view/{id}/configuration', [ + 'as' => 'admin.nodes.view.configuration', + 'uses' => 'Admin\NodesController@viewConfiguration', ]); - $router->post('/view/{id}/alias', [ - 'as' => 'admin.nodes.alias', - 'uses' => 'Admin\NodesController@setAlias', + $router->get('/view/{id}/allocation', [ + 'as' => 'admin.nodes.view.allocation', + 'uses' => 'Admin\NodesController@viewAllocation', ]); - $router->get('/view/{id}/allocations.json', [ - 'as' => 'admin.nodes.view.allocations', - 'uses' => 'Admin\NodesController@getAllocationsJson', + $router->post('/view/{id}/allocation', [ + 'uses' => 'Admin\NodesController@createAllocation', ]); - $router->post('/view/{id}/allocations', [ - 'as' => 'admin.nodes.post.allocations', - 'uses' => 'Admin\NodesController@postAllocations', + $router->get('/view/{id}/servers', [ + 'as' => 'admin.nodes.view.servers', + 'uses' => 'Admin\NodesController@viewServers', ]); - // View Deploy - $router->get('/view/{id}/deploy', [ - 'as' => 'admin.nodes.deply', - 'uses' => 'Admin\NodesController@getScript', + $router->delete('/view/{id}/delete', [ + 'as' => 'admin.nodes.view.delete', + 'uses' => 'Admin\NodesController@delete', ]); - $router->delete('/view/{id}', [ - 'as' => 'admin.nodes.delete', - 'uses' => 'Admin\NodesController@deleteNode', + $router->delete('/view/{id}/allocation/remove/{allocation}', [ + 'as' => 'admin.nodes.view.allocation.removeSingle', + 'uses' => 'Admin\NodesController@allocationRemoveSingle', ]); - $router->get('/{id}/configurationtoken', [ - 'as' => 'admin.nodes.configuration-token', - 'uses' => 'Admin\NodesController@getConfigurationToken', + $router->post('/view/{id}/allocation/remove', [ + 'as' => 'admin.nodes.view.allocation.removeBlock', + 'uses' => 'Admin\NodesController@allocationRemoveBlock', + ]); + + $router->post('/view/{id}/allocation/alias', [ + 'as' => 'admin.nodes.view.allocation.setAlias', + 'uses' => 'Admin\NodesController@allocationSetAlias', + ]); + + $router->get('/view/{id}/settings/token', [ + 'as' => 'admin.nodes.view.configuration.token', + 'uses' => 'Admin\NodesController@setToken', ]); }); diff --git a/app/Jobs/DeleteServer.php b/app/Jobs/DeleteServer.php index a42637ff2..16d04b4ba 100644 --- a/app/Jobs/DeleteServer.php +++ b/app/Jobs/DeleteServer.php @@ -58,6 +58,6 @@ class DeleteServer extends Job implements ShouldQueue public function handle() { $repo = new ServerRepository; - $repo->deleteNow($this->id); + $repo->delete($this->id); } } diff --git a/app/Models/Node.php b/app/Models/Node.php index 520df2a5e..928633095 100644 --- a/app/Models/Node.php +++ b/app/Models/Node.php @@ -27,10 +27,11 @@ namespace Pterodactyl\Models; use GuzzleHttp\Client; use Illuminate\Database\Eloquent\Model; use Illuminate\Notifications\Notifiable; +use Nicolaslopezj\Searchable\SearchableTrait; class Node extends Model { - use Notifiable; + use Notifiable, SearchableTrait; /** * The table associated with the model. @@ -74,6 +75,18 @@ class Node extends Model 'daemonSFTP', 'daemonListen', ]; + protected $searchable = [ + 'columns' => [ + 'nodes.name' => 10, + 'nodes.fqdn' => 8, + 'locations.short' => 4, + 'locations.long' => 4, + ], + 'joins' => [ + 'locations' => ['locations.id', 'nodes.location_id'], + ], + ]; + /** * Return an instance of the Guzzle client for this specific node. * @@ -117,10 +130,6 @@ class Node extends Model 'port' => $this->daemonSFTP, 'container' => 'ptdl-sftp', ], - 'query' => [ - 'kill_on_fail' => true, - 'fail_limit' => 5, - ], 'logger' => [ 'path' => 'logs/', 'src' => false, diff --git a/app/Models/NodeConfigurationToken.php b/app/Models/NodeConfigurationToken.php index dd029ec78..b09e096bd 100644 --- a/app/Models/NodeConfigurationToken.php +++ b/app/Models/NodeConfigurationToken.php @@ -48,4 +48,14 @@ class NodeConfigurationToken extends Model * @var array */ protected $dates = ['created_at', 'updated_at', 'expires_at']; + + /** + * Gets the node associated with a configuration token. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function node() + { + return $this->belongsTo(Node::class); + } } diff --git a/app/Models/Server.php b/app/Models/Server.php index 7c69c763f..524708cde 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -26,14 +26,16 @@ namespace Pterodactyl\Models; use Auth; use Cache; +use Carbon; use Javascript; use Illuminate\Database\Eloquent\Model; use Illuminate\Notifications\Notifiable; use Illuminate\Database\Eloquent\SoftDeletes; +use Nicolaslopezj\Searchable\SearchableTrait; class Server extends Model { - use Notifiable, SoftDeletes; + use Notifiable, SearchableTrait, SoftDeletes; /** * The table associated with the model. @@ -85,6 +87,22 @@ class Server extends Model 'installed' => 'integer', ]; + protected $searchable = [ + 'columns' => [ + 'servers.name' => 10, + 'servers.username' => 10, + 'servers.uuidShort' => 9, + 'servers.uuid' => 8, + 'users.email' => 6, + 'users.username' => 6, + 'nodes.name' => 2, + ], + 'joins' => [ + 'users' => ['users.id', 'servers.owner_id'], + 'nodes' => ['nodes.id', 'servers.node_id'], + ], + ]; + /** * Returns a single server specified by UUID. * DO NOT USE THIS TO MODIFY SERVER DETAILS OR SAVE THOSE DETAILS. @@ -95,8 +113,12 @@ class Server extends Model */ public static function byUuid($uuid) { + if (! Auth::check()) { + throw new \Exception('You must call Server:byUuid as an authenticated user.'); + } + // Results are cached because we call this functions a few times on page load. - $result = Cache::remember('Server.byUuid.' . $uuid . Auth::user()->uuid, 60, function () use ($uuid) { + $result = Cache::tags(['Model:Server', 'Model:Server:byUuid:' . $uuid])->remember('Model:Server:byUuid:' . $uuid . Auth::user()->uuid, Carbon::now()->addMinutes(15), function () use ($uuid) { $query = self::with('service', 'node')->where(function ($q) use ($uuid) { $q->where('uuidShort', $uuid)->orWhere('uuid', $uuid); }); diff --git a/app/Models/User.php b/app/Models/User.php index 131192264..b660e304c 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -30,6 +30,7 @@ use Illuminate\Auth\Authenticatable; use Illuminate\Database\Eloquent\Model; use Illuminate\Notifications\Notifiable; use Pterodactyl\Exceptions\DisplayException; +use Nicolaslopezj\Searchable\SearchableTrait; use Illuminate\Auth\Passwords\CanResetPassword; use Illuminate\Foundation\Auth\Access\Authorizable; use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; @@ -39,7 +40,7 @@ use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification; class User extends Model implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract { - use Authenticatable, Authorizable, CanResetPassword, Notifiable; + use Authenticatable, Authorizable, CanResetPassword, Notifiable, SearchableTrait; /** * The rules for user passwords. @@ -87,6 +88,16 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac */ protected $hidden = ['password', 'remember_token', 'totp_secret']; + protected $searchable = [ + 'columns' => [ + 'email' => 10, + 'username' => 9, + 'name_first' => 6, + 'name_last' => 6, + 'uuid' => 1, + ], + ]; + /** * Enables or disables TOTP on an account if the token is valid. * diff --git a/app/Observers/ServerObserver.php b/app/Observers/ServerObserver.php index 5d919a559..92d1b8f9a 100644 --- a/app/Observers/ServerObserver.php +++ b/app/Observers/ServerObserver.php @@ -24,7 +24,6 @@ namespace Pterodactyl\Observers; -use Auth; use Cache; use Carbon; use Pterodactyl\Events; @@ -41,8 +40,8 @@ class ServerObserver /** * Listen to the Server creating event. * - * @param Server $server [description] - * @return [type] [description] + * @param Server $server The server model. + * @return void */ public function creating(Server $server) { @@ -52,8 +51,8 @@ class ServerObserver /** * Listen to the Server created event. * - * @param Server $server [description] - * @return [type] [description] + * @param Server $server The server model. + * @return void */ public function created(Server $server) { @@ -73,8 +72,8 @@ class ServerObserver /** * Listen to the Server deleting event. * - * @param Server $server [description] - * @return [type] [description] + * @param Server $server The server model. + * @return void */ public function deleting(Server $server) { @@ -86,8 +85,8 @@ class ServerObserver /** * Listen to the Server deleted event. * - * @param Server $server [description] - * @return [type] [description] + * @param Server $server The server model. + * @return void */ public function deleted(Server $server) { @@ -103,8 +102,8 @@ class ServerObserver /** * Listen to the Server saving event. * - * @param Server $server [description] - * @return [type] [description] + * @param Server $server The server model. + * @return void */ public function saving(Server $server) { @@ -114,8 +113,8 @@ class ServerObserver /** * Listen to the Server saved event. * - * @param Server $server [description] - * @return [type] [description] + * @param Server $server The server model. + * @return void */ public function saved(Server $server) { @@ -123,10 +122,10 @@ class ServerObserver } /** - * Listen to the Server saving event. + * Listen to the Server updating event. * - * @param Server $server [description] - * @return [type] [description] + * @param Server $server The server model. + * @return void */ public function updating(Server $server) { @@ -136,14 +135,20 @@ class ServerObserver /** * Listen to the Server saved event. * - * @param Server $server [description] - * @return [type] [description] + * @param Server $server The server model. + * @return void */ public function updated(Server $server) { - // Clear Caches - Cache::forget('Server.byUuid.' . $server->uuid . Auth::user()->uuid); - Cache::forget('Server.byUuid.' . $server->uuidShort . Auth::user()->uuid); + /* + * The cached byUuid model calls are tagged with Model:Server:byUuid: + * so that they can be accessed regardless of if there is an Auth::user() + * defined or not. + * + * We can also delete all cached byUuid items using the Model:Server tag. + */ + Cache::tags('Model:Server:byUuid:' . $server->uuid)->flush(); + Cache::tags('Model:Server:byUuid:' . $server->uuidShort)->flush(); event(new Events\Server\Updated($server)); } diff --git a/app/Repositories/DatabaseRepository.php b/app/Repositories/DatabaseRepository.php index c605a32de..b0d85b60f 100644 --- a/app/Repositories/DatabaseRepository.php +++ b/app/Repositories/DatabaseRepository.php @@ -26,89 +26,99 @@ namespace Pterodactyl\Repositories; use DB; use Crypt; +use Config; use Validator; use Pterodactyl\Models; use Pterodactyl\Exceptions\DisplayException; -use Illuminate\Database\Capsule\Manager as Capsule; use Pterodactyl\Exceptions\DisplayValidationException; class DatabaseRepository { /** - * Adds a new database to a given database server. + * Adds a new database to a specified database host server. + * * @param int $server Id of the server to add a database for. * @param array $options Array of options for creating that database. + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\DisplayValidationException + * @throws \Exception * @return void */ - public function create($server, $options) + public function create($server, $data) { $server = Models\Server::findOrFail($server); - $validator = Validator::make($options, [ - 'db_server' => 'required|exists:database_servers,id', + + $validator = Validator::make($data, [ + 'host' => 'required|exists:database_servers,id', 'database' => 'required|regex:/^\w{1,100}$/', - 'remote' => 'required|regex:/^[0-9%.]{1,15}$/', + 'connection' => 'required|regex:/^[0-9%.]{1,15}$/', ]); if ($validator->fails()) { throw new DisplayValidationException($validator->errors()); } + $host = Models\DatabaseServer::findOrFail($data['host']); DB::beginTransaction(); + try { - $db = new Models\Database; - $db->fill([ + $database = Models\Database::firstOrNew([ 'server_id' => $server->id, - 'db_server' => $options['db_server'], - 'database' => "s{$server->id}_{$options['database']}", - 'username' => $server->uuidShort . '_' . str_random(7), - 'remote' => $options['remote'], - 'password' => Crypt::encrypt(str_random(20)), - ]); - $db->save(); - - // Contact Remote - $dbr = Models\DatabaseServer::findOrFail($options['db_server']); - - $capsule = new Capsule; - $capsule->addConnection([ - 'driver' => 'mysql', - 'host' => $dbr->host, - 'port' => $dbr->port, - 'database' => 'mysql', - 'username' => $dbr->username, - 'password' => Crypt::decrypt($dbr->password), - 'charset' => 'utf8', - 'collation' => 'utf8_unicode_ci', - 'prefix' => '', - 'options' => [ - \PDO::ATTR_TIMEOUT => 3, - ], + 'db_server' => $data['host'], + 'database' => sprintf('s%d_%s', $server->id, $data['database']), ]); - $capsule->setAsGlobal(); + if ($database->exists) { + throw new DisplayException('A database with those details already exists in the system.'); + } + + $database->username = sprintf('s%d_%s', $server->id, str_random(10)); + $database->remote = $data['connection']; + $database->password = Crypt::encrypt(str_random(20)); + + $database->save(); } catch (\Exception $ex) { DB::rollBack(); - throw new DisplayException('There was an error while connecting to the Database Host Server. Please check the error logs.', $ex); + throw $ex; } + Config::set('database.connections.dynamic', [ + 'driver' => 'mysql', + 'host' => $host->host, + 'port' => $host->port, + 'database' => 'mysql', + 'username' => $host->username, + 'password' => Crypt::decrypt($host->password), + 'charset' => 'utf8', + 'collation' => 'utf8_unicode_ci', + ]); + try { - Capsule::statement('CREATE DATABASE `' . $db->database . '`'); - Capsule::statement('CREATE USER `' . $db->username . '`@`' . $db->remote . '` IDENTIFIED BY \'' . Crypt::decrypt($db->password) . '\''); - Capsule::statement('GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, INDEX ON `' . $db->database . '`.* TO `' . $db->username . '`@`' . $db->remote . '`'); - Capsule::statement('FLUSH PRIVILEGES'); + DB::connection('dynamic')->statement(sprintf('CREATE DATABASE IF NOT EXISTS `%s`', $database->database)); + DB::connection('dynamic')->statement(sprintf( + 'CREATE USER `%s`@`%s` IDENTIFIED BY \'%s\'', + $database->username, $database->remote, Crypt::decrypt($database->password) + )); + DB::connection('dynamic')->statement(sprintf( + 'GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, INDEX ON `%s`.* TO `%s`@`%s`', + $database->database, $database->username, $database->remote + )); + + DB::connection('dynamic')->statement('FLUSH PRIVILEGES'); + + // Save Everything DB::commit(); } catch (\Exception $ex) { try { - Capsule::statement('DROP DATABASE `' . $db->database . '`'); - Capsule::statement('DROP USER `' . $db->username . '`@`' . $db->remote . '`'); - } catch (\Exception $exi) { - // ignore it, if it fails its probably - // because we failed to ever make the DB - // or the user on the system. - } finally { - DB::rollBack(); - throw $ex; + DB::connection('dynamic')->statement(sprintf('DROP DATABASE IF EXISTS `%s`', $database->database)); + DB::connection('dynamic')->statement(sprintf('DROP USER IF EXISTS `%s`@`%s`', $database->username, $database->remote)); + DB::connection('dynamic')->statement('FLUSH PRIVILEGES'); + } catch (\Exception $ex) { } + + DB::rollBack(); + throw $ex; } } @@ -118,7 +128,7 @@ class DatabaseRepository * @param string $password The new password to use for the database. * @return bool */ - public function modifyPassword($id, $password) + public function password($id, $password) { $database = Models\Database::with('host')->findOrFail($id); @@ -127,33 +137,25 @@ class DatabaseRepository $database->password = Crypt::encrypt($password); $database->save(); - $capsule = new Capsule; - $capsule->addConnection([ + Config::set('database.connections.dynamic', [ 'driver' => 'mysql', 'host' => $database->host->host, 'port' => $database->host->port, 'database' => 'mysql', 'username' => $database->host->username, 'password' => Crypt::decrypt($database->host->password), - 'charset' => 'utf8', + 'charset' => 'utf8', 'collation' => 'utf8_unicode_ci', - 'prefix' => '', - 'options' => [ - \PDO::ATTR_TIMEOUT => 3, - ], ]); - $capsule->setAsGlobal(); - Capsule::statement(sprintf( + DB::connection('dynamic')->statement(sprintf( 'SET PASSWORD FOR `%s`@`%s` = PASSWORD(\'%s\')', - $database->username, - $database->remote, - $password + $database->username, $database->remote, $password )); DB::commit(); } catch (\Exception $ex) { - DB::rollback(); + DB::rollBack(); throw $ex; } } @@ -168,34 +170,25 @@ class DatabaseRepository $database = Models\Database::with('host')->findOrFail($id); DB::beginTransaction(); - try { - $capsule = new Capsule; - $capsule->addConnection([ + Config::set('database.connections.dynamic', [ 'driver' => 'mysql', 'host' => $database->host->host, 'port' => $database->host->port, 'database' => 'mysql', 'username' => $database->host->username, 'password' => Crypt::decrypt($database->host->password), - 'charset' => 'utf8', + 'charset' => 'utf8', 'collation' => 'utf8_unicode_ci', - 'prefix' => '', - 'options' => [ - \PDO::ATTR_TIMEOUT => 3, - ], ]); - $capsule->setAsGlobal(); - - Capsule::statement('DROP USER `' . $database->username . '`@`' . $database->remote . '`'); - Capsule::statement('DROP DATABASE `' . $database->database . '`'); + DB::connection('dynamic')->statement(sprintf('DROP DATABASE IF EXISTS `%s`', $database->database)); + DB::connection('dynamic')->statement(sprintf('DROP USER IF EXISTS `%s`@`%s`', $database->username, $database->remote)); + DB::connection('dynamic')->statement('FLUSH PRIVILEGES'); $database->delete(); DB::commit(); - - return true; } catch (\Exception $ex) { DB::rollback(); throw $ex; @@ -243,28 +236,20 @@ class DatabaseRepository } DB::beginTransaction(); - try { - $capsule = new Capsule; - $capsule->addConnection([ + Config::set('database.connections.dynamic', [ 'driver' => 'mysql', 'host' => $data['host'], 'port' => $data['port'], 'database' => 'mysql', 'username' => $data['username'], 'password' => $data['password'], - 'charset' => 'utf8', + 'charset' => 'utf8', 'collation' => 'utf8_unicode_ci', - 'prefix' => '', - 'options' => [ - \PDO::ATTR_TIMEOUT => 3, - ], ]); - $capsule->setAsGlobal(); - // Allows us to check that we can connect to things. - Capsule::select('SELECT 1 FROM dual'); + DB::connection('dynamic')->select('SELECT 1 FROM dual'); Models\DatabaseServer::create([ 'name' => $data['name'], diff --git a/app/Repositories/NodeRepository.php b/app/Repositories/NodeRepository.php index 56dbd37c2..32b206991 100644 --- a/app/Repositories/NodeRepository.php +++ b/app/Repositories/NodeRepository.php @@ -177,80 +177,70 @@ class NodeRepository } } - public function addAllocations($id, array $allocations) + /** + * Adds allocations to a provided node. + * @param int $id + * @param array $data + */ + public function addAllocations($id, array $data) { $node = Models\Node::findOrFail($id); - DB::beginTransaction(); + $validator = Validator::make($data, [ + 'allocation_ip' => 'required|string', + 'allocation_alias' => 'sometimes|required|string|max:255', + 'allocation_ports' => 'required|array', + ]); - try { - foreach ($allocations as $rawIP => $ports) { - try { - $setAlias = null; - $parsedIP = Network::parse($rawIP); - } catch (\Exception $ex) { - try { - $setAlias = $rawIP; - $parsedIP = Network::parse(gethostbyname($rawIP)); - } catch (\Exception $ex) { - throw $ex; + if ($validator->fails()) { + throw new DisplayValidationException($validator->errors()); + } + + $explode = explode('/', $data['allocation_ip']); + if (count($explode) !== 1) { + if (! ctype_digit($explode[1]) || ($explode[1] > 32 || $explode[1] < 25)) { + throw new DisplayException('CIDR notation only allows masks between /32 and /25.'); + } + } + + DB::transaction(function () use ($parsed, $node, $data) { + foreach (Network::parse(gethostbyname($data['allocation_ip'])) as $ip) { + foreach ($data['allocation_ports'] as $port) { + // Determine if this is a valid single port, or a valid port range. + if (! ctype_digit($port) && ! preg_match('/^(\d{1,5})-(\d{1,5})$/', $port)) { + throw new DisplayException('The mapping for ' . $port . ' is invalid and cannot be processed.'); } - } - foreach ($parsedIP as $ip) { - foreach ($ports as $port) { - if (! is_int($port) && ! preg_match('/^(\d{1,5})-(\d{1,5})$/', $port)) { - throw new DisplayException('The mapping for ' . $port . ' is invalid and cannot be processed.'); + + if (preg_match('/^(\d{1,5})-(\d{1,5})$/', $port, $matches)) { + $block = range($matches[1], $matches[2]); + + if (count($block) > 1000) { + throw new DisplayException('Adding more than 1000 ports at once is not supported. Please use a smaller port range.'); } - if (preg_match('/^(\d{1,5})-(\d{1,5})$/', $port, $matches)) { - $portBlock = range($matches[1], $matches[2]); - if (count($portBlock) > 2000) { - throw new DisplayException('Adding more than 2000 ports at once is not currently supported. Please consider using a smaller port range.'); - } - - foreach ($portBlock as $assignPort) { - $alloc = Models\Allocation::firstOrNew([ - 'node_id' => $node->id, - 'ip' => $ip, - 'port' => $assignPort, - ]); - if (! $alloc->exists) { - $alloc->fill([ - 'node_id' => $node->id, - 'ip' => $ip, - 'port' => $assignPort, - 'ip_alias' => $setAlias, - 'server_id' => null, - ]); - $alloc->save(); - } - } - } else { - $alloc = Models\Allocation::firstOrNew([ + foreach ($block as $unit) { + // Insert into Database + Models\Allocation::firstOrCreate([ 'node_id' => $node->id, 'ip' => $ip, - 'port' => $port, + 'port' => $unit, + 'ip_alias' => isset($data['allocation_alias']) ? $data['allocation_alias'] : null, + 'server_id' => null, ]); - if (! $alloc->exists) { - $alloc->fill([ - 'node_id' => $node->id, - 'ip' => $ip, - 'port' => $port, - 'ip_alias' => $setAlias, - 'server_id' => null, - ]); - $alloc->save(); - } } + } else { + // Insert into Database + Models\Allocation::firstOrCreate([ + 'node_id' => $node->id, + 'ip' => $ip, + 'port' => $port, + 'ip_alias' => isset($data['allocation_alias']) ? $data['allocation_alias'] : null, + 'server_id' => null, + ]); } } } - - DB::commit(); - } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; - } + }); } public function delete($id) diff --git a/app/Repositories/ServerRepository.php b/app/Repositories/ServerRepository.php index 4b5ed1f15..0e4e0f099 100644 --- a/app/Repositories/ServerRepository.php +++ b/app/Repositories/ServerRepository.php @@ -83,7 +83,7 @@ class ServerRepository // Validate Fields $validator = Validator::make($data, [ - 'owner' => 'bail|required', + 'user_id' => 'required|exists:users,id', 'name' => 'required|regex:/^([\w .-]{1,200})$/', 'memory' => 'required|numeric|min:0', 'swap' => 'required|numeric|min:-1', @@ -95,25 +95,20 @@ class ServerRepository 'location_id' => 'required|numeric|min:1|exists:locations,id', 'pack_id' => 'sometimes|nullable|numeric|min:0', 'startup' => 'string', - 'custom_image_name' => 'required_if:use_custom_image,on', 'auto_deploy' => 'sometimes|boolean', 'custom_id' => 'sometimes|required|numeric|unique:servers,id', ]); - $validator->sometimes('node_id', 'bail|required|numeric|min:1|exists:nodes,id', function ($input) { + $validator->sometimes('node_id', 'required|numeric|min:1|exists:nodes,id', function ($input) { return ! ($input->auto_deploy); }); - $validator->sometimes('ip', 'required|ip', function ($input) { - return ! $input->auto_deploy && ! $input->allocation; + $validator->sometimes('allocation_id', 'required|numeric|exists:allocations,id', function ($input) { + return ! ($input->auto_deploy); }); - $validator->sometimes('port', 'required|numeric|min:1|max:65535', function ($input) { - return ! $input->auto_deploy && ! $input->allocation; - }); - - $validator->sometimes('allocation_id', 'numeric|exists:allocations,id', function ($input) { - return ! ($input->auto_deploy || ($input->port && $input->ip)); + $validator->sometimes('allocation_additional.*', 'sometimes|required|numeric|exists:allocations,id', function ($input) { + return ! ($input->auto_deploy); }); // Run validator, throw catchable and displayable exception if it fails. @@ -122,16 +117,13 @@ class ServerRepository throw new DisplayValidationException($validator->errors()); } - $user = Models\User::select('id', 'email')->where((is_int($data['owner'])) ? 'id' : 'email', $data['owner'])->first(); - if (! $user) { - throw new DisplayException('The user id or email passed to the function was not found on the system.'); - } + $user = Models\User::findOrFail($data['user_id']); $autoDeployed = false; - if (isset($data['auto_deploy']) && in_array($data['auto_deploy'], [true, 1, '1'])) { + if (isset($data['auto_deploy']) && $data['auto_deploy']) { // This is an auto-deployment situation // Ignore any other passed node data - unset($data['node_id'], $data['ip'], $data['port'], $data['allocation_id']); + unset($data['node_id'], $data['allocation_id']); $autoDeployed = true; $node = DeploymentService::smartRandomNode($data['memory'], $data['disk'], $data['location_id']); @@ -143,17 +135,12 @@ class ServerRepository // Verify IP & Port are a.) free and b.) assigned to the node. // We know the node exists because of 'exists:nodes,id' in the validation if (! $autoDeployed) { - if (! isset($data['allocation_id'])) { - $model = Models\Allocation::where('ip', $data['ip'])->where('port', $data['port']); - } else { - $model = Models\Allocation::where('id', $data['allocation_id']); - } - $allocation = $model->where('node_id', $data['node_id'])->whereNull('server_id')->first(); + $allocation = Models\Allocation::where('id', $data['allocation_id'])->where('node_id', $data['node_id'])->whereNull('server_id')->first(); } // Something failed in the query, either that combo doesn't exist, or it is in use. if (! $allocation) { - throw new DisplayException('The selected IP/Port combination or Allocation ID is either already in use, or unavaliable for this node.'); + throw new DisplayException('The selected Allocation ID is either already in use, or unavaliable for this node.'); } // Validate those Service Option Variables @@ -166,11 +153,9 @@ class ServerRepository } // Validate the Pack - if ($data['pack_id'] == 0) { + if (! isset($data['pack_id']) || (int) $data['pack_id'] < 1) { $data['pack_id'] = null; - } - - if (! is_null($data['pack_id'])) { + } else { $pack = Models\ServicePack::where('id', $data['pack_id'])->where('option_id', $data['option_id'])->first(); if (! $pack) { throw new DisplayException('The requested service pack does not seem to exist for this combination.'); @@ -188,7 +173,7 @@ class ServerRepository // Is the variable required? if (! isset($data['env_' . $variable->env_variable])) { - if ($variable->required === 1) { + if ($variable->required) { throw new DisplayException('A required service option variable field (env_' . $variable->env_variable . ') was missing from the request.'); } $variableList[] = [ @@ -271,7 +256,7 @@ class ServerRepository 'pack_id' => $data['pack_id'], 'startup' => $data['startup'], 'daemonSecret' => $uuid->generate('servers', 'daemonSecret'), - 'image' => (isset($data['custom_image_name'])) ? $data['custom_image_name'] : $option->docker_image, + 'image' => (isset($data['custom_container'])) ? $data['custom_container'] : $option->docker_image, 'username' => $this->generateSFTPUsername($data['name'], $genShortUuid), 'sftp_password' => Crypt::encrypt('not set'), ]); @@ -281,6 +266,19 @@ class ServerRepository $allocation->server_id = $server->id; $allocation->save(); + // Add Additional Allocations + if (isset($data['allocation_additional']) && is_array($data['allocation_additional'])) { + foreach ($data['allocation_additional'] as $allocation) { + $model = Models\Allocation::where('id', $allocation)->where('node_id', $data['node_id'])->whereNull('server_id')->first(); + if (! $model) { + continue; + } + + $model->server_id = $server->id; + $model->save(); + } + } + // Add Variables $environmentVariables = [ 'STARTUP' => $data['startup'], @@ -296,25 +294,26 @@ class ServerRepository ]); } + $server->load('allocation', 'allocations'); $node->guzzleClient(['X-Access-Token' => $node->daemonSecret])->request('POST', '/servers', [ 'json' => [ 'uuid' => (string) $server->uuid, 'user' => $server->username, 'build' => [ 'default' => [ - 'ip' => $allocation->ip, - 'port' => (int) $allocation->port, - ], - 'ports' => [ - (string) $allocation->ip => [(int) $allocation->port], + 'ip' => $server->allocation->ip, + 'port' => $server->allocation->port, ], + 'ports' => $server->allocations->groupBy('ip')->map(function ($item) { + return $item->pluck('port'); + })->toArray(), 'env' => $environmentVariables, 'memory' => (int) $server->memory, 'swap' => (int) $server->swap, 'io' => (int) $server->io, 'cpu' => (int) $server->cpu, 'disk' => (int) $server->disk, - 'image' => (isset($data['custom_image_name'])) ? $data['custom_image_name'] : $option->docker_image, + 'image' => (isset($data['custom_container'])) ? $data['custom_container'] : $option->docker_image, ], 'service' => [ 'type' => $service->file, @@ -353,8 +352,9 @@ class ServerRepository // Validate Fields $validator = Validator::make($data, [ - 'owner' => 'email|exists:users,email', - 'name' => 'regex:([\w .-]{1,200})', + 'owner_id' => 'sometimes|required|integer|exists:users,id', + 'name' => 'sometimes|required|regex:([\w .-]{1,200})', + 'reset_token' => 'sometimes|required|accepted', ]); // Run validator, throw catchable and displayable exception if it fails. @@ -369,16 +369,15 @@ class ServerRepository $server = Models\Server::with('user')->findOrFail($id); // Update daemon secret if it was passed. - if ((isset($data['reset_token']) && $data['reset_token'] === true) || (isset($data['owner']) && $data['owner'] !== $server->user->email)) { + if (isset($data['reset_token']) || (isset($data['owner_id']) && (int) $data['owner_id'] !== $server->user->id)) { $oldDaemonKey = $server->daemonSecret; $server->daemonSecret = $uuid->generate('servers', 'daemonSecret'); $resetDaemonKey = true; } // Update Server Owner if it was passed. - if (isset($data['owner']) && $data['owner'] !== $server->user->email) { - $newOwner = Models\User::select('id')->where('email', $data['owner'])->first(); - $server->owner_id = $newOwner->id; + if (isset($data['owner_id']) && (int) $data['owner_id'] !== $server->user->id) { + $server->owner_id = $data['owner_id']; } // Update Server Name if it was passed. @@ -432,7 +431,7 @@ class ServerRepository public function updateContainer($id, array $data) { $validator = Validator::make($data, [ - 'image' => 'required|string', + 'docker_image' => 'required|string', ]); // Run validator, throw catchable and displayable exception if it fails. @@ -445,7 +444,7 @@ class ServerRepository try { $server = Models\Server::findOrFail($id); - $server->image = $data['image']; + $server->image = $data['docker_image']; $server->save(); $server->node->guzzleClient([ @@ -464,7 +463,7 @@ class ServerRepository return true; } catch (TransferException $ex) { DB::rollBack(); - throw new DisplayException('An error occured while attempting to update the container image.', $ex); + throw new DisplayException('A TransferException occured while attempting to update the container image. Is the daemon online? This error has been logged.', $ex); } catch (\Exception $ex) { DB::rollBack(); throw $ex; @@ -480,17 +479,14 @@ class ServerRepository public function changeBuild($id, array $data) { $validator = Validator::make($data, [ - 'default' => [ - 'string', - 'regex:/^(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5])):(\d{1,5})$/', - ], - 'add_additional' => 'nullable|array', - 'remove_additional' => 'nullable|array', - 'memory' => 'integer|min:0', - 'swap' => 'integer|min:-1', - 'io' => 'integer|min:10|max:1000', - 'cpu' => 'integer|min:0', - 'disk' => 'integer|min:0', + 'allocation_id' => 'sometimes|required|exists:allocations,id', + 'add_allocations' => 'sometimes|required|array', + 'remove_allocations' => 'sometimes|required|array', + 'memory' => 'sometimes|required|integer|min:0', + 'swap' => 'sometimes|required|integer|min:-1', + 'io' => 'sometimes|required|integer|min:10|max:1000', + 'cpu' => 'sometimes|required|integer|min:0', + 'disk' => 'sometimes|required|integer|min:0', ]); // Run validator, throw catchable and displayable exception if it fails. @@ -504,43 +500,33 @@ class ServerRepository try { $server = Models\Server::with('allocation', 'allocations')->findOrFail($id); $newBuild = []; + $newAllocations = []; - if (isset($data['default'])) { - list($ip, $port) = explode(':', $data['default']); - if ($ip !== $server->allocation->ip || (int) $port !== $server->allocation->port) { - $selection = $server->allocations->where('ip', $ip)->where('port', $port)->first(); + if (isset($data['allocation_id'])) { + if ((int) $data['allocation_id'] !== $server->allocation_id) { + $selection = $server->allocations->where('id', $data['allocation_id'])->first(); if (! $selection) { - throw new DisplayException('The requested default connection (' . $ip . ':' . $port . ') is not allocated to this server.'); + throw new DisplayException('The requested default connection is not allocated to this server.'); } $server->allocation_id = $selection->id; - $newBuild['default'] = [ - 'ip' => $ip, - 'port' => (int) $port, - ]; + $newBuild['default'] = ['ip' => $selection->ip, 'port' => $selection->port]; - // Re-Run to keep updated for rest of function $server->load('allocation'); } } $newPorts = false; // Remove Assignments - if (isset($data['remove_additional'])) { - foreach ($data['remove_additional'] as $id => $combo) { - list($ip, $port) = explode(':', $combo); - // Invalid, not worth killing the whole thing, we'll just skip over it. - if (! filter_var($ip, FILTER_VALIDATE_IP) || ! preg_match('/^(\d{1,5})$/', $port)) { - break; - } - + if (isset($data['remove_allocations'])) { + foreach ($data['remove_allocations'] as $allocation) { // Can't remove the assigned IP/Port combo - if ($ip === $server->allocation->ip && (int) $port === (int) $server->allocation->port) { - break; + if ((int) $allocation === $server->allocation_id) { + continue; } $newPorts = true; - $server->allocations->where('ip', $ip)->where('port', $port)->update([ + Models\Allocation::where('id', $allocation)->where('server_id', $server->id)->update([ 'server_id' => null, ]); } @@ -549,21 +535,15 @@ class ServerRepository } // Add Assignments - if (isset($data['add_additional'])) { - foreach ($data['add_additional'] as $id => $combo) { - list($ip, $port) = explode(':', $combo); - // Invalid, not worth killing the whole thing, we'll just skip over it. - if (! filter_var($ip, FILTER_VALIDATE_IP) || ! preg_match('/^(\d{1,5})$/', $port)) { - break; - } - - // Don't allow double port assignments - if ($server->allocations->where('port', $port)->count() !== 0) { - break; + if (isset($data['add_allocations'])) { + foreach ($data['add_allocations'] as $allocation) { + $model = Models\Allocation::where('id', $allocation)->whereNull('server_id')->first(); + if (! $model) { + continue; } $newPorts = true; - Models\Allocation::where('ip', $ip)->where('port', $port)->whereNull('server_id')->update([ + $model->update([ 'server_id' => $server->id, ]); } @@ -571,18 +551,10 @@ class ServerRepository $server->load('allocations'); } - // Loop All Assignments - $additionalAssignments = []; - foreach ($server->allocations as &$assignment) { - if (array_key_exists((string) $assignment->ip, $additionalAssignments)) { - array_push($additionalAssignments[(string) $assignment->ip], (int) $assignment->port); - } else { - $additionalAssignments[(string) $assignment->ip] = [(int) $assignment->port]; - } - } - - if ($newPorts === true) { - $newBuild['ports|overwrite'] = $additionalAssignments; + if ($newPorts) { + $newBuild['ports|overwrite'] = $server->allocations->groupBy('ip')->map(function ($item) { + return $item->pluck('port'); + })->toArray(); } // @TODO: verify that server can be set to this much memory without @@ -631,10 +603,10 @@ class ServerRepository DB::commit(); - return true; + return $server; } catch (TransferException $ex) { DB::rollBack(); - throw new DisplayException('An error occured while attempting to update the configuration.', $ex); + throw new DisplayException('A TransferException occured while attempting to update the server configuration, check that the daemon is online. This error has been logged.', $ex); } catch (\Exception $ex) { DB::rollBack(); throw $ex; @@ -645,87 +617,63 @@ class ServerRepository { $server = Models\Server::with('variables', 'option.variables')->findOrFail($id); - DB::beginTransaction(); - - try { - // Check the startup + DB::transaction(function () use ($admin, $data, $server) { if (isset($data['startup']) && $admin) { $server->startup = $data['startup']; $server->save(); } - // Check those Variables - $server->option->variables->transform(function ($item, $key) use ($server) { - $displayValue = $server->variables->where('variable_id', $item->id)->pluck('variable_value')->first(); - $item->server_value = (! is_null($displayValue)) ? $displayValue : $item->default_value; - - return $item; - }); - - $variableList = []; if ($server->option->variables) { foreach ($server->option->variables as &$variable) { - // Move on if the new data wasn't even sent - if (! isset($data[$variable->env_variable])) { - $variableList[] = [ - 'id' => $variable->id, - 'env' => $variable->env_variable, - 'val' => $variable->server_value, - ]; + $set = isset($data['env_' . $variable->id]); + + // Variable is required but was not passed into the function. + if ($variable->required && ! $set) { + throw new DisplayException('A required variable (' . $variable->env_variable . ') was not passed in the request.'); + } + + // If user is not an admin and are trying to edit a non-editable field + // or an invisible field just silently skip the variable. + if (! $admin && (! $variable->user_editable || ! $variable->user_viewable)) { continue; } - // Update Empty but skip validation - if (empty($data[$variable->env_variable])) { - $variableList[] = [ - 'id' => $variable->id, - 'env' => $variable->env_variable, - 'val' => null, - ]; - continue; - } - - // Is the variable required? - // @TODO: is this even logical to perform this check? - if (isset($data[$variable->env_variable]) && empty($data[$variable->env_variable])) { - if ($variable->required) { - throw new DisplayException('A required service option variable field (' . $variable->env_variable . ') was included in this request but was left blank.'); + // Confirm value is valid when compared aganist regex. + // @TODO: switch to Laravel validation rules. + if ($set && ! is_null($variable->regex)) { + if (! preg_match($variable->regex, $data['env_' . $variable->id])) { + throw new DisplayException('The value passed for a variable (' . $variable->env_variable . ') could not be matched aganist the regex for that field (' . $variable->regex . ').'); } } - // Variable hidden and/or not user editable - if ((! $variable->user_viewable || ! $variable->user_editable) && ! $admin) { - throw new DisplayException('A service option variable field (' . $variable->env_variable . ') does not exist or you do not have permission to edit it.'); + $svar = Models\ServerVariable::firstOrNew([ + 'server_id' => $server->id, + 'variable_id' => $variable->id, + ]); + + // Set the value; if one was not passed set it to the default value + if ($set) { + $svar->variable_value = $data['env_' . $variable->id]; + + // Not passed, check if this record exists if so keep value, otherwise set default + } else { + $svar->variable_value = ($svar->exists) ? $svar->variable_value : $variable->default_value; } - // Check aganist Regex Pattern - if (! is_null($variable->regex) && ! preg_match($variable->regex, $data[$variable->env_variable])) { - throw new DisplayException('Failed to validate service option variable field (' . $variable->env_variable . ') aganist regex (' . $variable->regex . ').'); - } - - $variableList[] = [ - 'id' => $variable->id, - 'env' => $variable->env_variable, - 'val' => $data[$variable->env_variable], - ]; + $svar->save(); } } - // Add Variables - $environmentVariables = [ - 'STARTUP' => $server->startup, - ]; - foreach ($variableList as $item) { - $environmentVariables[$item['env']] = $item['val']; + // Reload Variables + $server->load('variables'); + $environment = $server->option->variables->map(function ($item, $key) use ($server) { + $display = $server->variables->where('variable_id', $item->id)->pluck('variable_value')->first(); - // Update model or make a new record if it doesn't exist. - $model = Models\ServerVariable::firstOrNew([ - 'variable_id' => $item['id'], - 'server_id' => $server->id, - ]); - $model->variable_value = $item['val']; - $model->save(); - } + return [ + 'variable' => $item->env_variable, + 'value' => (! is_null($display)) ? $display : $item->default_value, + ]; + }); $server->node->guzzleClient([ 'X-Access-Server' => $server->uuid, @@ -733,30 +681,20 @@ class ServerRepository ])->request('PATCH', '/server', [ 'json' => [ 'build' => [ - 'env|overwrite' => $environmentVariables, + 'env|overwrite' => $environment->pluck('value', 'variable')->merge(['STARTUP' => $server->startup]), ], ], ]); - - DB::commit(); - - return true; - } catch (TransferException $ex) { - DB::rollBack(); - throw new DisplayException('An error occured while attempting to update the server configuration.', $ex); - } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; - } + }); } - public function deleteServer($id, $force) + public function queueDeletion($id, $force = false) { $server = Models\Server::findOrFail($id); DB::beginTransaction(); try { - if ($force === 'force' || $force) { + if ($force) { $server->installed = 3; $server->save(); } @@ -770,7 +708,7 @@ class ServerRepository } } - public function deleteNow($id, $force = false) + public function delete($id, $force = false) { $server = Models\Server::withTrashed()->with('node')->findOrFail($id); @@ -807,10 +745,12 @@ class ServerRepository // Delete Databases // This is the one un-recoverable point where // transactions will not save us. - $repository = new DatabaseRepository; - foreach (Models\Database::select('id')->where('server_id', $server->id)->get() as &$database) { - $repository->drop($database->id); - } + // + // @TODO: move to post-deletion event as a queued task! + // $repository = new DatabaseRepository; + // foreach (Models\Database::select('id')->where('server_id', $server->id)->get() as &$database) { + // $repository->drop($database->id); + // } $server->node->guzzleClient([ 'X-Access-Token' => $server->node->daemonSecret, @@ -846,8 +786,8 @@ class ServerRepository public function toggleInstall($id) { $server = Models\Server::findOrFail($id); - if ($server->installed === 2) { - throw new DisplayException('This server was marked as having a failed install, you cannot override this.'); + if ($server->installed > 1) { + throw new DisplayException('This server was marked as having a failed install or being deleted, you cannot override this.'); } $server->installed = ! $server->installed; diff --git a/composer.json b/composer.json index 40bee8bcb..a95d82df1 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,8 @@ "predis/predis": "1.1.1", "fideloper/proxy": "3.2.0", "laracasts/utilities": "2.1.0", - "lord/laroute": "2.3.0" + "lord/laroute": "2.3.0", + "nicolaslopezj/searchable": "1.9.5" }, "require-dev": { "fzaninotto/faker": "~1.4", diff --git a/composer.lock b/composer.lock index 22e5c4e3b..535af7aa2 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "49714983a18ad2bba4759ccde45314c5", - "content-hash": "af8cd5b69f96dd17c1e02afc1ba8e467", + "hash": "6a9656aff0fb3809d27a2a093a810197", + "content-hash": "8affaad10f155172b5079a72015b8bc5", "packages": [ { "name": "aws/aws-sdk-php", @@ -2011,6 +2011,52 @@ ], "time": "2015-11-04 20:07:17" }, + { + "name": "nicolaslopezj/searchable", + "version": "1.9.5", + "source": { + "type": "git", + "url": "https://github.com/nicolaslopezj/searchable.git", + "reference": "1351b1b21ae9be9e0f49f375f56488df839723d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nicolaslopezj/searchable/zipball/1351b1b21ae9be9e0f49f375f56488df839723d4", + "reference": "1351b1b21ae9be9e0f49f375f56488df839723d4", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "illuminate/database": "4.2.x|~5.0", + "php": ">=5.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Nicolaslopezj\\Searchable\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Lopez", + "email": "nicolaslopezj@me.com" + } + ], + "description": "Eloquent model search trait.", + "keywords": [ + "database", + "eloquent", + "laravel", + "model", + "search", + "searchable" + ], + "time": "2016-12-16 21:23:34" + }, { "name": "nikic/php-parser", "version": "v2.1.1", diff --git a/config/javascript.php b/config/javascript.php index 2a5cda584..1c2ef82dc 100644 --- a/config/javascript.php +++ b/config/javascript.php @@ -15,6 +15,7 @@ return [ */ 'bind_js_vars_to_this_view' => [ 'layouts.master', + 'layouts.admin', ], /* diff --git a/database/migrations/2017_03_03_224254_UpdateNodeConfigTokensColumns.php b/database/migrations/2017_03_03_224254_UpdateNodeConfigTokensColumns.php new file mode 100644 index 000000000..5931518d6 --- /dev/null +++ b/database/migrations/2017_03_03_224254_UpdateNodeConfigTokensColumns.php @@ -0,0 +1,40 @@ +dropForeign(['node']); + $table->dropColumn('expires_at'); + $table->renameColumn('node', 'node_id'); + + $table->foreign('node_id')->references('id')->on('nodes'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('node_configuration_tokens', function (Blueprint $table) { + $table->dropForeign(['node_id']); + $table->renameColumn('node_id', 'node'); + $table->timestamp('expires_at')->after('token'); + + $table->foreign('node')->references('id')->on('nodes'); + }); + } +} diff --git a/public/js/laroute.js b/public/js/laroute.js index 633b139f9..b4f553912 100644 --- a/public/js/laroute.js +++ b/public/js/laroute.js @@ -6,7 +6,7 @@ absolute: false, rootUrl: 'http://pterodactyl.app', - routes : [{"host":null,"methods":["GET","HEAD"],"uri":"admin","name":"admin.index","action":"Pterodactyl\Http\Controllers\Admin\BaseController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/settings","name":"admin.settings","action":"Pterodactyl\Http\Controllers\Admin\BaseController@getSettings"},{"host":null,"methods":["POST"],"uri":"admin\/settings","name":null,"action":"Pterodactyl\Http\Controllers\Admin\BaseController@postSettings"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users","name":"admin.users","action":"Pterodactyl\Http\Controllers\Admin\UserController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/accounts.json","name":"admin.users.json","action":"Pterodactyl\Http\Controllers\Admin\UserController@getJson"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/view\/{id}","name":"admin.users.view","action":"Pterodactyl\Http\Controllers\Admin\UserController@getView"},{"host":null,"methods":["POST"],"uri":"admin\/users\/view\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@updateUser"},{"host":null,"methods":["DELETE"],"uri":"admin\/users\/view\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@deleteUser"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/new","name":"admin.users.new","action":"Pterodactyl\Http\Controllers\Admin\UserController@getNew"},{"host":null,"methods":["POST"],"uri":"admin\/users\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@postNew"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers","name":"admin.servers","action":"Pterodactyl\Http\Controllers\Admin\ServersController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/new","name":"admin.servers.new","action":"Pterodactyl\Http\Controllers\Admin\ServersController@getNew"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@postNewServer"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/new\/get-nodes","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@postNewServerGetNodes"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/new\/get-ips","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@postNewServerGetIps"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/new\/service-options","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@postNewServerServiceOption"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/new\/option-details","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@postNewServerOptionDetails"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{id}","name":"admin.servers.view","action":"Pterodactyl\Http\Controllers\Admin\ServersController@getView"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/database","name":"admin.servers.database","action":"Pterodactyl\Http\Controllers\Admin\ServersController@postDatabase"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/details","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@postUpdateServerDetails"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/container","name":"admin.servers.post.container","action":"Pterodactyl\Http\Controllers\Admin\ServersController@postUpdateContainerDetails"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/startup","name":"admin.servers.post.startup","action":"Pterodactyl\Http\Controllers\Admin\ServersController@postUpdateServerStartup"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/rebuild","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@postUpdateServerToggleBuild"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/build","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@postUpdateServerUpdateBuild"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/suspend","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@postSuspendServer"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/unsuspend","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@postUnsuspendServer"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/installed","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@postToggleInstall"},{"host":null,"methods":["DELETE"],"uri":"admin\/servers\/view\/{id}\/{force?}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@deleteServer"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/queuedDeletion","name":"admin.servers.post.queuedDeletion","action":"Pterodactyl\Http\Controllers\Admin\ServersController@postQueuedDeletionHandler"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes","name":"admin.nodes","action":"Pterodactyl\Http\Controllers\Admin\NodesController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/new","name":"admin.nodes.new","action":"Pterodactyl\Http\Controllers\Admin\NodesController@getNew"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@postNew"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{id}","name":"admin.nodes.view","action":"Pterodactyl\Http\Controllers\Admin\NodesController@getView"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@postView"},{"host":null,"methods":["DELETE"],"uri":"admin\/nodes\/view\/{id}\/deallocate\/single\/{allocation}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@deallocateSingle"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{id}\/deallocate\/block","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@deallocateBlock"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{id}\/alias","name":"admin.nodes.alias","action":"Pterodactyl\Http\Controllers\Admin\NodesController@setAlias"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{id}\/allocations.json","name":"admin.nodes.view.allocations","action":"Pterodactyl\Http\Controllers\Admin\NodesController@getAllocationsJson"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{id}\/allocations","name":"admin.nodes.post.allocations","action":"Pterodactyl\Http\Controllers\Admin\NodesController@postAllocations"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{id}\/deploy","name":"admin.nodes.deply","action":"Pterodactyl\Http\Controllers\Admin\NodesController@getScript"},{"host":null,"methods":["DELETE"],"uri":"admin\/nodes\/view\/{id}","name":"admin.nodes.delete","action":"Pterodactyl\Http\Controllers\Admin\NodesController@deleteNode"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/{id}\/configurationtoken","name":"admin.nodes.configuration-token","action":"Pterodactyl\Http\Controllers\Admin\NodesController@getConfigurationToken"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/locations","name":"admin.locations","action":"Pterodactyl\Http\Controllers\Admin\LocationsController@getIndex"},{"host":null,"methods":["DELETE"],"uri":"admin\/locations\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\LocationsController@deleteLocation"},{"host":null,"methods":["PATCH"],"uri":"admin\/locations\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\LocationsController@patchLocation"},{"host":null,"methods":["POST"],"uri":"admin\/locations","name":null,"action":"Pterodactyl\Http\Controllers\Admin\LocationsController@postLocation"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/databases","name":"admin.databases","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/databases\/new","name":"admin.databases.new","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@getNew"},{"host":null,"methods":["POST"],"uri":"admin\/databases\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@postNew"},{"host":null,"methods":["DELETE"],"uri":"admin\/databases\/delete\/{id}","name":"admin.databases.delete","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@deleteDatabase"},{"host":null,"methods":["DELETE"],"uri":"admin\/databases\/delete-server\/{id}","name":"admin.databases.delete-server","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@deleteServer"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services","name":"admin.services","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/new","name":"admin.services.new","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@getNew"},{"host":null,"methods":["POST"],"uri":"admin\/services\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@postNew"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/service\/{id}","name":"admin.services.service","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@getService"},{"host":null,"methods":["POST"],"uri":"admin\/services\/service\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@postService"},{"host":null,"methods":["DELETE"],"uri":"admin\/services\/service\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@deleteService"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/service\/{id}\/configuration","name":"admin.services.service.config","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@getConfiguration"},{"host":null,"methods":["POST"],"uri":"admin\/services\/service\/{id}\/configuration","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@postConfiguration"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/service\/{service}\/option\/new","name":"admin.services.option.new","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@newOption"},{"host":null,"methods":["POST"],"uri":"admin\/services\/service\/{service}\/option\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@postNewOption"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/service\/{service}\/option\/{option}","name":"admin.services.option","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@getOption"},{"host":null,"methods":["POST"],"uri":"admin\/services\/service\/{service}\/option\/{option}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@postOption"},{"host":null,"methods":["DELETE"],"uri":"admin\/services\/service\/{service}\/option\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@deleteOption"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/service\/{service}\/option\/{option}\/variable\/new","name":"admin.services.option.variable.new","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@getNewVariable"},{"host":null,"methods":["POST"],"uri":"admin\/services\/service\/{service}\/option\/{option}\/variable\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@postNewVariable"},{"host":null,"methods":["POST"],"uri":"admin\/services\/service\/{service}\/option\/{option}\/variable\/{variable}","name":"admin.services.option.variable","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@postOptionVariable"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/service\/{service}\/option\/{option}\/variable\/{variable}\/delete","name":"admin.services.option.variable.delete","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@deleteVariable"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/packs\/new\/{option?}","name":"admin.services.packs.new","action":"Pterodactyl\Http\Controllers\Admin\PackController@new"},{"host":null,"methods":["POST"],"uri":"admin\/services\/packs\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/packs\/upload\/{option?}","name":"admin.services.packs.uploadForm","action":"Pterodactyl\Http\Controllers\Admin\PackController@uploadForm"},{"host":null,"methods":["POST"],"uri":"admin\/services\/packs\/upload","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@postUpload"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/packs","name":"admin.services.packs","action":"Pterodactyl\Http\Controllers\Admin\PackController@listAll"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/packs\/for\/option\/{option}","name":"admin.services.packs.option","action":"Pterodactyl\Http\Controllers\Admin\PackController@listByOption"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/packs\/for\/service\/{service}","name":"admin.services.packs.service","action":"Pterodactyl\Http\Controllers\Admin\PackController@listByService"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/packs\/edit\/{pack}","name":"admin.services.packs.edit","action":"Pterodactyl\Http\Controllers\Admin\PackController@edit"},{"host":null,"methods":["POST"],"uri":"admin\/services\/packs\/edit\/{pack}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/packs\/edit\/{pack}\/export\/{archive?}","name":"admin.services.packs.export","action":"Pterodactyl\Http\Controllers\Admin\PackController@export"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/login","name":"auth.login","action":"Pterodactyl\Http\Controllers\Auth\LoginController@showLoginForm"},{"host":null,"methods":["POST"],"uri":"auth\/login","name":null,"action":"Pterodactyl\Http\Controllers\Auth\LoginController@login"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/login\/totp","name":"auth.totp","action":"Pterodactyl\Http\Controllers\Auth\LoginController@totp"},{"host":null,"methods":["POST"],"uri":"auth\/login\/totp","name":null,"action":"Pterodactyl\Http\Controllers\Auth\LoginController@totpCheckpoint"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/password","name":"auth.password","action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@showLinkRequestForm"},{"host":null,"methods":["POST"],"uri":"auth\/password","name":null,"action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/password\/reset\/{token}","name":"auth.reset","action":"Pterodactyl\Http\Controllers\Auth\ResetPasswordController@showResetForm"},{"host":null,"methods":["POST"],"uri":"auth\/password\/reset","name":"auth.reset.post","action":"Pterodactyl\Http\Controllers\Auth\ResetPasswordController@reset"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/logout","name":"auth.logout","action":"Pterodactyl\Http\Controllers\Auth\LoginController@logout"},{"host":null,"methods":["GET","HEAD"],"uri":"\/","name":"index","action":"Pterodactyl\Http\Controllers\Base\IndexController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"index","name":null,"action":"Closure"},{"host":null,"methods":["GET","HEAD"],"uri":"password-gen\/{length}","name":"password-gen","action":"Pterodactyl\Http\Controllers\Base\IndexController@getPassword"},{"host":null,"methods":["GET","HEAD"],"uri":"account","name":"account","action":"Pterodactyl\Http\Controllers\Base\AccountController@index"},{"host":null,"methods":["POST"],"uri":"account","name":null,"action":"Pterodactyl\Http\Controllers\Base\AccountController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/api","name":"account.api","action":"Pterodactyl\Http\Controllers\Base\APIController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/api\/new","name":"account.api.new","action":"Pterodactyl\Http\Controllers\Base\APIController@create"},{"host":null,"methods":["POST"],"uri":"account\/api\/new","name":null,"action":"Pterodactyl\Http\Controllers\Base\APIController@save"},{"host":null,"methods":["DELETE"],"uri":"account\/api\/revoke\/{key}","name":"account.api.revoke","action":"Pterodactyl\Http\Controllers\Base\APIController@revoke"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/security","name":"account.security","action":"Pterodactyl\Http\Controllers\Base\SecurityController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/security\/revoke\/{id}","name":"account.security.revoke","action":"Pterodactyl\Http\Controllers\Base\SecurityController@revoke"},{"host":null,"methods":["PUT"],"uri":"account\/security\/totp","name":"account.security.totp","action":"Pterodactyl\Http\Controllers\Base\SecurityController@generateTotp"},{"host":null,"methods":["POST"],"uri":"account\/security\/totp","name":null,"action":"Pterodactyl\Http\Controllers\Base\SecurityController@setTotp"},{"host":null,"methods":["DELETE"],"uri":"account\/security\/totp","name":null,"action":"Pterodactyl\Http\Controllers\Base\SecurityController@disableTotp"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/services","name":"daemon.services","action":"Pterodactyl\Http\Controllers\Daemon\ServiceController@list"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/services\/pull\/{service}\/{file}","name":"remote.install","action":"Pterodactyl\Http\Controllers\Daemon\ServiceController@pull"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/packs\/pull\/{uuid}","name":"daemon.pack.pull","action":"Pterodactyl\Http\Controllers\Daemon\PackController@pull"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/packs\/pull\/{uuid}\/hash","name":"daemon.pack.hash","action":"Pterodactyl\Http\Controllers\Daemon\PackController@hash"},{"host":null,"methods":["GET","HEAD"],"uri":"language\/{lang}","name":"langauge.set","action":"Pterodactyl\Http\Controllers\Base\LanguageController@setLanguage"},{"host":null,"methods":["POST"],"uri":"remote\/download","name":"remote.download","action":"Pterodactyl\Http\Controllers\Remote\RemoteController@postDownload"},{"host":null,"methods":["POST"],"uri":"remote\/install","name":"remote.install","action":"Pterodactyl\Http\Controllers\Remote\RemoteController@postInstall"},{"host":null,"methods":["GET","HEAD"],"uri":"remote\/configuration\/{token}","name":"remote.configuration","action":"Pterodactyl\Http\Controllers\Remote\RemoteController@getConfiguration"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}","name":"server.index","action":"Pterodactyl\Http\Controllers\Server\ServerController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/databases","name":"server.settings.databases","action":"Pterodactyl\Http\Controllers\Server\ServerController@getDatabases"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/sftp","name":"server.settings.sftp","action":"Pterodactyl\Http\Controllers\Server\ServerController@getSFTP"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/settings\/sftp","name":null,"action":"Pterodactyl\Http\Controllers\Server\ServerController@postSettingsSFTP"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/startup","name":"server.settings.startup","action":"Pterodactyl\Http\Controllers\Server\ServerController@getStartup"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/settings\/startup","name":null,"action":"Pterodactyl\Http\Controllers\Server\ServerController@postSettingsStartup"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/allocation","name":"server.settings.allocation","action":"Pterodactyl\Http\Controllers\Server\ServerController@getAllocation"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files","name":"server.files.index","action":"Pterodactyl\Http\Controllers\Server\ServerController@getFiles"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/edit\/{file}","name":"server.files.edit","action":"Pterodactyl\Http\Controllers\Server\ServerController@getEditFile"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/download\/{file}","name":"server.files.download","action":"Pterodactyl\Http\Controllers\Server\ServerController@getDownloadFile"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/add","name":"server.files.add","action":"Pterodactyl\Http\Controllers\Server\ServerController@getAddFile"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/files\/directory-list","name":"server.files.directory-list","action":"Pterodactyl\Http\Controllers\Server\AjaxController@postDirectoryList"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/files\/save","name":"server.files.save","action":"Pterodactyl\Http\Controllers\Server\AjaxController@postSaveFile"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users","name":"server.subusers","action":"Pterodactyl\Http\Controllers\Server\SubuserController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users\/new","name":"server.subusers.new","action":"Pterodactyl\Http\Controllers\Server\SubuserController@getNew"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/users\/new","name":null,"action":"Pterodactyl\Http\Controllers\Server\SubuserController@postNew"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users\/view\/{id}","name":"server.subusers.view","action":"Pterodactyl\Http\Controllers\Server\SubuserController@getView"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/users\/view\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Server\SubuserController@postView"},{"host":null,"methods":["DELETE"],"uri":"server\/{server}\/users\/delete\/{id}","name":"server.subusers.delete","action":"Pterodactyl\Http\Controllers\Server\SubuserController@deleteSubuser"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/tasks","name":"server.tasks","action":"Pterodactyl\Http\Controllers\Server\TaskController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/tasks\/view\/{id}","name":"server.tasks.view","action":"Pterodactyl\Http\Controllers\Server\TaskController@getView"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/tasks\/new","name":"server.tasks.new","action":"Pterodactyl\Http\Controllers\Server\TaskController@getNew"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/tasks\/new","name":null,"action":"Pterodactyl\Http\Controllers\Server\TaskController@postNew"},{"host":null,"methods":["DELETE"],"uri":"server\/{server}\/tasks\/delete\/{id}","name":"server.tasks.delete","action":"Pterodactyl\Http\Controllers\Server\TaskController@deleteTask"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/tasks\/toggle\/{id}","name":"server.tasks.toggle","action":"Pterodactyl\Http\Controllers\Server\TaskController@toggleTask"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/ajax\/status","name":"server.ajax.status","action":"Pterodactyl\Http\Controllers\Server\AjaxController@getStatus"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/ajax\/set-primary","name":null,"action":"Pterodactyl\Http\Controllers\Server\AjaxController@postSetPrimary"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/ajax\/settings\/reset-database-password","name":"server.ajax.reset-database-password","action":"Pterodactyl\Http\Controllers\Server\AjaxController@postResetDatabasePassword"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/open","name":"debugbar.openhandler","action":"Barryvdh\Debugbar\Controllers\OpenHandlerController@handle"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/clockwork\/{id}","name":"debugbar.clockwork","action":"Barryvdh\Debugbar\Controllers\OpenHandlerController@clockwork"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/assets\/stylesheets","name":"debugbar.assets.css","action":"Barryvdh\Debugbar\Controllers\AssetController@css"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/assets\/javascript","name":"debugbar.assets.js","action":"Barryvdh\Debugbar\Controllers\AssetController@js"}], + routes : [{"host":null,"methods":["GET","HEAD"],"uri":"admin","name":"admin.index","action":"Pterodactyl\Http\Controllers\Admin\BaseController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/settings","name":"admin.settings","action":"Pterodactyl\Http\Controllers\Admin\BaseController@getSettings"},{"host":null,"methods":["POST"],"uri":"admin\/settings","name":null,"action":"Pterodactyl\Http\Controllers\Admin\BaseController@postSettings"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users","name":"admin.users","action":"Pterodactyl\Http\Controllers\Admin\UserController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/accounts.json","name":"admin.users.json","action":"Pterodactyl\Http\Controllers\Admin\UserController@getJson"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/view\/{id}","name":"admin.users.view","action":"Pterodactyl\Http\Controllers\Admin\UserController@getView"},{"host":null,"methods":["POST"],"uri":"admin\/users\/view\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@updateUser"},{"host":null,"methods":["DELETE"],"uri":"admin\/users\/view\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@deleteUser"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/new","name":"admin.users.new","action":"Pterodactyl\Http\Controllers\Admin\UserController@getNew"},{"host":null,"methods":["POST"],"uri":"admin\/users\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@postNew"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers","name":"admin.servers","action":"Pterodactyl\Http\Controllers\Admin\ServersController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/new","name":"admin.servers.new","action":"Pterodactyl\Http\Controllers\Admin\ServersController@new"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@create"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/new\/nodes","name":"admin.servers.new.nodes","action":"Pterodactyl\Http\Controllers\Admin\ServersController@newServerNodes"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{id}","name":"admin.servers.view","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{id}\/details","name":"admin.servers.view.details","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewDetails"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/details","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@setDetails"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/details\/container","name":"admin.servers.view.details.container","action":"Pterodactyl\Http\Controllers\Admin\ServersController@setContainer"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{id}\/build","name":"admin.servers.view.build","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewBuild"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/build","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@updateBuild"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{id}\/startup","name":"admin.servers.view.startup","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewStartup"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/startup","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@saveStartup"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{id}\/database","name":"admin.servers.view.database","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewDatabase"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/database","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@newDatabase"},{"host":null,"methods":["PATCH"],"uri":"admin\/servers\/view\/{id}\/database","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@resetDatabasePassword"},{"host":null,"methods":["DELETE"],"uri":"admin\/servers\/view\/{id}\/database\/{database}\/delete","name":"admin.servers.view.database.delete","action":"Pterodactyl\Http\Controllers\Admin\ServersController@deleteDatabase"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{id}\/manage","name":"admin.servers.view.manage","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewManage"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/manage\/toggle","name":"admin.servers.view.manage.toggle","action":"Pterodactyl\Http\Controllers\Admin\ServersController@toggleInstall"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/manage\/rebuild","name":"admin.servers.view.manage.rebuild","action":"Pterodactyl\Http\Controllers\Admin\ServersController@rebuildContainer"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/manage\/suspension","name":"admin.servers.view.manage.suspension","action":"Pterodactyl\Http\Controllers\Admin\ServersController@manageSuspension"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{id}\/delete","name":"admin.servers.view.delete","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewDelete"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/delete","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@delete"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/delete\/continue\/{force?}","name":"admin.servers.view.delete.continue","action":"Pterodactyl\Http\Controllers\Admin\ServersController@continueDeletion"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{id}\/delete\/cancel","name":"admin.servers.view.delete.cancel","action":"Pterodactyl\Http\Controllers\Admin\ServersController@cancelDeletion"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes","name":"admin.nodes","action":"Pterodactyl\Http\Controllers\Admin\NodesController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/new","name":"admin.nodes.new","action":"Pterodactyl\Http\Controllers\Admin\NodesController@new"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{id}","name":"admin.nodes.view","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{id}\/settings","name":"admin.nodes.view.settings","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewSettings"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{id}\/settings","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@updateSettings"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{id}\/configuration","name":"admin.nodes.view.configuration","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewConfiguration"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{id}\/allocation","name":"admin.nodes.view.allocation","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewAllocation"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{id}\/allocation","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@createAllocation"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{id}\/servers","name":"admin.nodes.view.servers","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewServers"},{"host":null,"methods":["DELETE"],"uri":"admin\/nodes\/view\/{id}\/delete","name":"admin.nodes.view.delete","action":"Pterodactyl\Http\Controllers\Admin\NodesController@delete"},{"host":null,"methods":["DELETE"],"uri":"admin\/nodes\/view\/{id}\/allocation\/remove\/{allocation}","name":"admin.nodes.view.allocation.removeSingle","action":"Pterodactyl\Http\Controllers\Admin\NodesController@allocationRemoveSingle"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{id}\/allocation\/remove","name":"admin.nodes.view.allocation.removeBlock","action":"Pterodactyl\Http\Controllers\Admin\NodesController@allocationRemoveBlock"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{id}\/allocation\/alias","name":"admin.nodes.view.allocation.setAlias","action":"Pterodactyl\Http\Controllers\Admin\NodesController@allocationSetAlias"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{id}\/settings\/token","name":"admin.nodes.view.configuration.token","action":"Pterodactyl\Http\Controllers\Admin\NodesController@setToken"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/locations","name":"admin.locations","action":"Pterodactyl\Http\Controllers\Admin\LocationsController@getIndex"},{"host":null,"methods":["DELETE"],"uri":"admin\/locations\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\LocationsController@deleteLocation"},{"host":null,"methods":["PATCH"],"uri":"admin\/locations\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\LocationsController@patchLocation"},{"host":null,"methods":["POST"],"uri":"admin\/locations","name":null,"action":"Pterodactyl\Http\Controllers\Admin\LocationsController@postLocation"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/databases","name":"admin.databases","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/databases\/new","name":"admin.databases.new","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@getNew"},{"host":null,"methods":["POST"],"uri":"admin\/databases\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@postNew"},{"host":null,"methods":["DELETE"],"uri":"admin\/databases\/delete\/{id}","name":"admin.databases.delete","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@deleteDatabase"},{"host":null,"methods":["DELETE"],"uri":"admin\/databases\/delete-server\/{id}","name":"admin.databases.delete-server","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@deleteServer"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services","name":"admin.services","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/new","name":"admin.services.new","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@getNew"},{"host":null,"methods":["POST"],"uri":"admin\/services\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@postNew"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/service\/{id}","name":"admin.services.service","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@getService"},{"host":null,"methods":["POST"],"uri":"admin\/services\/service\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@postService"},{"host":null,"methods":["DELETE"],"uri":"admin\/services\/service\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@deleteService"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/service\/{id}\/configuration","name":"admin.services.service.config","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@getConfiguration"},{"host":null,"methods":["POST"],"uri":"admin\/services\/service\/{id}\/configuration","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@postConfiguration"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/service\/{service}\/option\/new","name":"admin.services.option.new","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@newOption"},{"host":null,"methods":["POST"],"uri":"admin\/services\/service\/{service}\/option\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@postNewOption"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/service\/{service}\/option\/{option}","name":"admin.services.option","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@getOption"},{"host":null,"methods":["POST"],"uri":"admin\/services\/service\/{service}\/option\/{option}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@postOption"},{"host":null,"methods":["DELETE"],"uri":"admin\/services\/service\/{service}\/option\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@deleteOption"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/service\/{service}\/option\/{option}\/variable\/new","name":"admin.services.option.variable.new","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@getNewVariable"},{"host":null,"methods":["POST"],"uri":"admin\/services\/service\/{service}\/option\/{option}\/variable\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@postNewVariable"},{"host":null,"methods":["POST"],"uri":"admin\/services\/service\/{service}\/option\/{option}\/variable\/{variable}","name":"admin.services.option.variable","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@postOptionVariable"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/service\/{service}\/option\/{option}\/variable\/{variable}\/delete","name":"admin.services.option.variable.delete","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@deleteVariable"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/packs\/new\/{option?}","name":"admin.services.packs.new","action":"Pterodactyl\Http\Controllers\Admin\PackController@new"},{"host":null,"methods":["POST"],"uri":"admin\/services\/packs\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/packs\/upload\/{option?}","name":"admin.services.packs.uploadForm","action":"Pterodactyl\Http\Controllers\Admin\PackController@uploadForm"},{"host":null,"methods":["POST"],"uri":"admin\/services\/packs\/upload","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@postUpload"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/packs","name":"admin.services.packs","action":"Pterodactyl\Http\Controllers\Admin\PackController@listAll"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/packs\/for\/option\/{option}","name":"admin.services.packs.option","action":"Pterodactyl\Http\Controllers\Admin\PackController@listByOption"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/packs\/for\/service\/{service}","name":"admin.services.packs.service","action":"Pterodactyl\Http\Controllers\Admin\PackController@listByService"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/packs\/edit\/{pack}","name":"admin.services.packs.edit","action":"Pterodactyl\Http\Controllers\Admin\PackController@edit"},{"host":null,"methods":["POST"],"uri":"admin\/services\/packs\/edit\/{pack}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/packs\/edit\/{pack}\/export\/{archive?}","name":"admin.services.packs.export","action":"Pterodactyl\Http\Controllers\Admin\PackController@export"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/login","name":"auth.login","action":"Pterodactyl\Http\Controllers\Auth\LoginController@showLoginForm"},{"host":null,"methods":["POST"],"uri":"auth\/login","name":null,"action":"Pterodactyl\Http\Controllers\Auth\LoginController@login"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/login\/totp","name":"auth.totp","action":"Pterodactyl\Http\Controllers\Auth\LoginController@totp"},{"host":null,"methods":["POST"],"uri":"auth\/login\/totp","name":null,"action":"Pterodactyl\Http\Controllers\Auth\LoginController@totpCheckpoint"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/password","name":"auth.password","action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@showLinkRequestForm"},{"host":null,"methods":["POST"],"uri":"auth\/password","name":null,"action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/password\/reset\/{token}","name":"auth.reset","action":"Pterodactyl\Http\Controllers\Auth\ResetPasswordController@showResetForm"},{"host":null,"methods":["POST"],"uri":"auth\/password\/reset","name":"auth.reset.post","action":"Pterodactyl\Http\Controllers\Auth\ResetPasswordController@reset"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/logout","name":"auth.logout","action":"Pterodactyl\Http\Controllers\Auth\LoginController@logout"},{"host":null,"methods":["GET","HEAD"],"uri":"\/","name":"index","action":"Pterodactyl\Http\Controllers\Base\IndexController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"index","name":null,"action":"Closure"},{"host":null,"methods":["GET","HEAD"],"uri":"password-gen\/{length}","name":"password-gen","action":"Pterodactyl\Http\Controllers\Base\IndexController@getPassword"},{"host":null,"methods":["GET","HEAD"],"uri":"account","name":"account","action":"Pterodactyl\Http\Controllers\Base\AccountController@index"},{"host":null,"methods":["POST"],"uri":"account","name":null,"action":"Pterodactyl\Http\Controllers\Base\AccountController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/api","name":"account.api","action":"Pterodactyl\Http\Controllers\Base\APIController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/api\/new","name":"account.api.new","action":"Pterodactyl\Http\Controllers\Base\APIController@create"},{"host":null,"methods":["POST"],"uri":"account\/api\/new","name":null,"action":"Pterodactyl\Http\Controllers\Base\APIController@save"},{"host":null,"methods":["DELETE"],"uri":"account\/api\/revoke\/{key}","name":"account.api.revoke","action":"Pterodactyl\Http\Controllers\Base\APIController@revoke"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/security","name":"account.security","action":"Pterodactyl\Http\Controllers\Base\SecurityController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/security\/revoke\/{id}","name":"account.security.revoke","action":"Pterodactyl\Http\Controllers\Base\SecurityController@revoke"},{"host":null,"methods":["PUT"],"uri":"account\/security\/totp","name":"account.security.totp","action":"Pterodactyl\Http\Controllers\Base\SecurityController@generateTotp"},{"host":null,"methods":["POST"],"uri":"account\/security\/totp","name":null,"action":"Pterodactyl\Http\Controllers\Base\SecurityController@setTotp"},{"host":null,"methods":["DELETE"],"uri":"account\/security\/totp","name":null,"action":"Pterodactyl\Http\Controllers\Base\SecurityController@disableTotp"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/services","name":"daemon.services","action":"Pterodactyl\Http\Controllers\Daemon\ServiceController@list"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/services\/pull\/{service}\/{file}","name":"remote.install","action":"Pterodactyl\Http\Controllers\Daemon\ServiceController@pull"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/packs\/pull\/{uuid}","name":"daemon.pack.pull","action":"Pterodactyl\Http\Controllers\Daemon\PackController@pull"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/packs\/pull\/{uuid}\/hash","name":"daemon.pack.hash","action":"Pterodactyl\Http\Controllers\Daemon\PackController@hash"},{"host":null,"methods":["GET","HEAD"],"uri":"language\/{lang}","name":"langauge.set","action":"Pterodactyl\Http\Controllers\Base\LanguageController@setLanguage"},{"host":null,"methods":["POST"],"uri":"remote\/download","name":"remote.download","action":"Pterodactyl\Http\Controllers\Remote\RemoteController@postDownload"},{"host":null,"methods":["POST"],"uri":"remote\/install","name":"remote.install","action":"Pterodactyl\Http\Controllers\Remote\RemoteController@postInstall"},{"host":null,"methods":["GET","HEAD"],"uri":"remote\/configuration\/{token}","name":"remote.configuration","action":"Pterodactyl\Http\Controllers\Remote\RemoteController@getConfiguration"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/ajax\/status","name":"server.ajax.status","action":"Pterodactyl\Http\Controllers\Server\AjaxController@getStatus"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}","name":"server.index","action":"Pterodactyl\Http\Controllers\Server\ServerController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/databases","name":"server.settings.databases","action":"Pterodactyl\Http\Controllers\Server\ServerController@getDatabases"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/sftp","name":"server.settings.sftp","action":"Pterodactyl\Http\Controllers\Server\ServerController@getSFTP"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/settings\/sftp","name":null,"action":"Pterodactyl\Http\Controllers\Server\ServerController@postSettingsSFTP"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/startup","name":"server.settings.startup","action":"Pterodactyl\Http\Controllers\Server\ServerController@getStartup"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/settings\/startup","name":null,"action":"Pterodactyl\Http\Controllers\Server\ServerController@postSettingsStartup"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/allocation","name":"server.settings.allocation","action":"Pterodactyl\Http\Controllers\Server\ServerController@getAllocation"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files","name":"server.files.index","action":"Pterodactyl\Http\Controllers\Server\ServerController@getFiles"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/edit\/{file}","name":"server.files.edit","action":"Pterodactyl\Http\Controllers\Server\ServerController@getEditFile"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/download\/{file}","name":"server.files.download","action":"Pterodactyl\Http\Controllers\Server\ServerController@getDownloadFile"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/add","name":"server.files.add","action":"Pterodactyl\Http\Controllers\Server\ServerController@getAddFile"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/files\/directory-list","name":"server.files.directory-list","action":"Pterodactyl\Http\Controllers\Server\AjaxController@postDirectoryList"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/files\/save","name":"server.files.save","action":"Pterodactyl\Http\Controllers\Server\AjaxController@postSaveFile"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users","name":"server.subusers","action":"Pterodactyl\Http\Controllers\Server\SubuserController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users\/new","name":"server.subusers.new","action":"Pterodactyl\Http\Controllers\Server\SubuserController@getNew"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/users\/new","name":null,"action":"Pterodactyl\Http\Controllers\Server\SubuserController@postNew"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users\/view\/{id}","name":"server.subusers.view","action":"Pterodactyl\Http\Controllers\Server\SubuserController@getView"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/users\/view\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\Server\SubuserController@postView"},{"host":null,"methods":["DELETE"],"uri":"server\/{server}\/users\/delete\/{id}","name":"server.subusers.delete","action":"Pterodactyl\Http\Controllers\Server\SubuserController@deleteSubuser"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/tasks","name":"server.tasks","action":"Pterodactyl\Http\Controllers\Server\TaskController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/tasks\/view\/{id}","name":"server.tasks.view","action":"Pterodactyl\Http\Controllers\Server\TaskController@getView"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/tasks\/new","name":"server.tasks.new","action":"Pterodactyl\Http\Controllers\Server\TaskController@getNew"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/tasks\/new","name":null,"action":"Pterodactyl\Http\Controllers\Server\TaskController@postNew"},{"host":null,"methods":["DELETE"],"uri":"server\/{server}\/tasks\/delete\/{id}","name":"server.tasks.delete","action":"Pterodactyl\Http\Controllers\Server\TaskController@deleteTask"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/tasks\/toggle\/{id}","name":"server.tasks.toggle","action":"Pterodactyl\Http\Controllers\Server\TaskController@toggleTask"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/ajax\/set-primary","name":null,"action":"Pterodactyl\Http\Controllers\Server\AjaxController@postSetPrimary"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/ajax\/settings\/reset-database-password","name":"server.ajax.reset-database-password","action":"Pterodactyl\Http\Controllers\Server\AjaxController@postResetDatabasePassword"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/open","name":"debugbar.openhandler","action":"Barryvdh\Debugbar\Controllers\OpenHandlerController@handle"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/clockwork\/{id}","name":"debugbar.clockwork","action":"Barryvdh\Debugbar\Controllers\OpenHandlerController@clockwork"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/assets\/stylesheets","name":"debugbar.assets.css","action":"Barryvdh\Debugbar\Controllers\AssetController@css"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/assets\/javascript","name":"debugbar.assets.js","action":"Barryvdh\Debugbar\Controllers\AssetController@js"}], prefix: '', route : function (name, parameters, route) { diff --git a/public/themes/pterodactyl/css/pterodactyl.css b/public/themes/pterodactyl/css/pterodactyl.css index e5fefe52f..ca34e46a5 100644 --- a/public/themes/pterodactyl/css/pterodactyl.css +++ b/public/themes/pterodactyl/css/pterodactyl.css @@ -65,7 +65,7 @@ code { font-size: 14px !important; } -.middle { +.middle, .align-middle { vertical-align: middle !important; } @@ -114,7 +114,7 @@ td.has-progress { .input-loader { display: none; position:relative; - top: -23px; + top: -25px; float: right; right: 5px; color: #cccccc; @@ -133,6 +133,119 @@ td.has-progress { margin: 0 !important; } +li.select2-results__option--highlighted[aria-selected="false"] > .user-block > .username > a { + color: #fff; +} + +li.select2-results__option--highlighted[aria-selected="false"] > .user-block > .description { + color: #eee; +} + +.select2-selection.select2-selection--multiple { + min-height: 36px !important; +} + +.select2-search--inline .select2-search__field:focus { + outline: none; + border: 0 !important; +} + +.img-bordered-xs { + border: 1px solid #d2d6de; + padding: 1px; +} + +span[aria-labelledby="select2-pUserId-container"] { + padding-left: 2px !important; +} + +.callout-slim a { + color: #555 !important; +} + +.callout.callout-info.callout-slim { + border: 1px solid #0097bc !important; + border-left: 5px solid #0097bc !important; + border-right: 5px solid #0097bc !important; + color: #777 !important; + background: transparent !important; +} + +.callout.callout-danger.callout-slim { + border: 1px solid #c23321 !important; + border-left: 5px solid #c23321 !important; + border-right: 5px solid #c23321 !important; + color: #777 !important; + background: transparent !important; +} + +.callout.callout-warning.callout-slim { + border: 1px solid #c87f0a !important; + border-left: 5px solid #c87f0a !important; + border-right: 5px solid #c87f0a !important; + color: #777 !important; + background: transparent !important; +} + +.callout.callout-success.callout-slim { + border: 1px solid #00733e !important; + border-left: 5px solid #00733e !important; + border-right: 5px solid #00733e !important; + color: #777 !important; + background: transparent !important; +} + +.callout.callout-default.callout-slim { + border: 1px solid #eee !important; + border-left: 5px solid #eee !important; + border-right: 5px solid #eee !important; + color: #777 !important; + background: transparent !important; +} + +.tab-pane .box-footer { + margin: 0 -10px -10px; +} + +.select2-container{ width: 100% !important; } + +.nav-tabs-custom > .nav-tabs > li:hover { + border-top-color:#3c8dbc; +} + +.nav-tabs-custom > .nav-tabs > li.active.tab-danger, .nav-tabs-custom > .nav-tabs > li.tab-danger:hover { + border-top-color: #c23321; +} + +.nav-tabs-custom > .nav-tabs > li.active.tab-success, .nav-tabs-custom > .nav-tabs > li.tab-success:hover { + border-top-color: #00733e; +} + +.nav-tabs-custom > .nav-tabs > li.active.tab-info, .nav-tabs-custom > .nav-tabs > li.tab-info:hover { + border-top-color: #0097bc; +} + +.nav-tabs-custom > .nav-tabs > li.active.tab-warning, .nav-tabs-custom > .nav-tabs > li.tab-warning:hover { + border-top-color: #c87f0a; +} + +.nav-tabs-custom.nav-tabs-floating > .nav-tabs { + border-bottom: 0px !important; +} + +.nav-tabs-custom.nav-tabs-floating > .nav-tabs > li { + margin-bottom: 0px !important; +} + +.nav-tabs-custom.nav-tabs-floating > .nav-tabs > li:first-child.active, +.nav-tabs-custom.nav-tabs-floating > .nav-tabs > li:first-child:hover { + border-radius: 3px 0 0 0; +} + +.nav-tabs-custom.nav-tabs-floating > .nav-tabs > li:first-child.active > a { + border-radius: 0 0 0 3px; +} + .position-relative { position: relative; } diff --git a/public/themes/pterodactyl/js/admin/functions.js b/public/themes/pterodactyl/js/admin/functions.js new file mode 100644 index 000000000..26115cd46 --- /dev/null +++ b/public/themes/pterodactyl/js/admin/functions.js @@ -0,0 +1,23 @@ +// Copyright (c) 2015 - 2017 Dane Everitt +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +$.urlParam=function(name){var results=new RegExp("[\\?&]"+name+"=([^&#]*)").exec(decodeURIComponent(window.location.href));if(results==null){return null}else{return results[1]||0}};function getPageName(url){var index=url.lastIndexOf("/")+1;var filenameWithExtension=url.substr(index);var filename=filenameWithExtension.split(".")[0];return filename} +// Remeber Active Tab and Navigate to it on Reload +for(var queryParameters={},queryString=location.search.substring(1),re=/([^&=]+)=([^&]*)/g,m;m=re.exec(queryString);)queryParameters[decodeURIComponent(m[1])]=decodeURIComponent(m[2]);$("a[data-toggle='tab']").click(function(){queryParameters.tab=$(this).attr("href").substring(1),window.history.pushState(null,null,location.pathname+"?"+$.param(queryParameters))}); +if($.urlParam('tab') != null){$('.nav.nav-tabs a[href="#' + $.urlParam('tab') + '"]').tab('show');} diff --git a/public/themes/pterodactyl/js/admin/new-server.js b/public/themes/pterodactyl/js/admin/new-server.js new file mode 100644 index 000000000..3e82cb1ad --- /dev/null +++ b/public/themes/pterodactyl/js/admin/new-server.js @@ -0,0 +1,195 @@ +// Copyright (c) 2015 - 2017 Dane Everitt +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +$(document).ready(function() { + $('#pServiceId').select2({ + placeholder: 'Select a Service', + }).change(); + $('#pOptionId').select2({ + placeholder: 'Select a Service Option', + }); + $('#pPackId').select2({ + placeholder: 'Select a Service Pack', + }); + $('#pLocationId').select2({ + placeholder: 'Select a Location', + }).change(); + $('#pNodeId').select2({ + placeholder: 'Select a Node', + }); + $('#pAllocation').select2({ + placeholder: 'Select a Default Allocation', + }); + $('#pAllocationAdditional').select2({ + placeholder: 'Select Additional Allocations', + }); + + $('#pUserId').select2({ + ajax: { + url: Router.route('admin.users.json'), + dataType: 'json', + delay: 250, + data: function (params) { + return { + q: params.term, // search term + page: params.page, + }; + }, + processResults: function (data, params) { + return { results: data }; + }, + cache: true, + }, + escapeMarkup: function (markup) { return markup; }, + minimumInputLength: 2, + templateResult: function (data) { + if (data.loading) return data.text; + + return '
\ + User Image \ + \ + ' + data.name_first + ' ' + data.name_last +' \ + \ + ' + data.email + ' - ' + data.username + ' \ +
'; + }, + templateSelection: function (data) { + return '
\ + \ + User Image \ + \ + \ + ' + data.name_first + ' ' + data.name_last + ' (' + data.email + ') \ + \ +
'; + } + }); +}); + +function hideLoader() { + $('#allocationLoader').hide(); +} + +function showLoader() { + $('#allocationLoader').show(); +} + +var lastActiveBox = null; +$(document).on('click', function (event) { + if (lastActiveBox !== null) { + lastActiveBox.removeClass('box-primary'); + } + + lastActiveBox = $(event.target).closest('.box'); + lastActiveBox.addClass('box-primary'); +}); + +var currentLocation = null; +var curentNode = null; +var NodeData = []; + +$('#pLocationId').on('change', function (event) { + showLoader(); + currentLocation = $(this).val(); + currentNode = null; + + $.ajax({ + method: 'POST', + url: Router.route('admin.servers.new.nodes'), + headers: { 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content') }, + data: { location: currentLocation }, + }).done(function (data) { + NodeData = data; + $('#pNodeId').html('').select2({data: data}).change(); + }).fail(function (jqXHR) { + cosole.error(jqXHR); + currentLocation = null; + }).always(hideLoader); +}); + +$('#pNodeId').on('change', function (event) { + currentNode = $(this).val(); + $.each(NodeData, function (i, v) { + if (v.id == currentNode) { + $('#pAllocation').html('').select2({ + data: v.allocations, + placeholder: 'Select a Default Allocation', + }); + $('#pAllocationAdditional').html('').select2({ + data: v.allocations, + placeholder: 'Select Additional Allocations', + }) + } + }); +}); + +$('#pServiceId').on('change', function (event) { + $('#pOptionId').html('').select2({ + data: $.map(_.get(Pterodactyl.services, $(this).val() + '.options', []), function (item) { + return { + id: item.id, + text: item.name, + }; + }), + }).change(); +}); + +$('#pOptionId').on('change', function (event) { + var parentChain = _.get(Pterodactyl.services, $('#pServiceId').val(), null); + var objectChain = _.get(parentChain, 'options.' + $(this).val(), null); + + $('#pDefaultContainer').val(_.get(objectChain, 'docker_image', 'not defined!')); + + if (!_.get(objectChain, 'startup', false)) { + $('#pStartup').val(_.get(parentChain, 'startup', 'ERROR: Startup Not Defined!')); + } else { + $('#pStartup').val(_.get(objectChain, 'startup')); + } + + if (!_.get(objectChain, 'executable', false)) { + $('#pStartupExecutable').html(_.get(parentChain, 'executable', 'ERROR: Exec Not Defined!')); + } else { + $('#pStartupExecutable').html(_.get(objectChain, 'executable')); + } + $('#pPackId').html('').select2({ + data: [{ id: 0, text: 'No Service Pack' }].concat( + $.map(_.get(objectChain, 'packs', []), function (item, i) { + return { + id: item.id, + text: item.name + ' (' + item.version + ')', + }; + }) + ), + }); + + $('#appendVariablesTo').html(''); + $.each(_.get(objectChain, 'variables', []), function (i, item) { + var isRequired = (item.required === 1) ? 'Required ' : ''; + var dataAppend = ' \ +
\ + \ + \ +

' + item.description + '
\ + Access in Startup: @{{' + item.env_variable + '}}
\ + Validation Regex: ' + item.regex + '

\ +
\ + '; + $('#appendVariablesTo').append(dataAppend); + }); +}); diff --git a/public/themes/pterodactyl/js/admin/node/view-servers.js b/public/themes/pterodactyl/js/admin/node/view-servers.js new file mode 100644 index 000000000..512dbc794 --- /dev/null +++ b/public/themes/pterodactyl/js/admin/node/view-servers.js @@ -0,0 +1,111 @@ +// Copyright (c) 2015 - 2017 Dane Everitt +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +(function initSocket() { + if (typeof $.notifyDefaults !== 'function') { + console.error('Notify does not appear to be loaded.'); + return; + } + + if (typeof io !== 'function') { + console.error('Socket.io is reqired to use this panel.'); + return; + } + + $.notifyDefaults({ + placement: { + from: 'bottom', + align: 'right' + }, + newest_on_top: true, + delay: 2000, + animate: { + enter: 'animated zoomInDown', + exit: 'animated zoomOutDown' + } + }); + + var notifySocketError = false; + // Main Socket Object + window.Socket = io(Pterodactyl.node.scheme + '://' + Pterodactyl.node.fqdn + ':' + Pterodactyl.node.daemonListen + '/stats/', { + 'query': 'token=' + Pterodactyl.node.daemonSecret, + }); + + // Socket Failed to Connect + Socket.io.on('connect_error', function (err) { + if(typeof notifySocketError !== 'object') { + notifySocketError = $.notify({ + message: 'There was an error attempting to establish a WebSocket connection to the Daemon. This panel will not work as expected.

' + err, + }, { + type: 'danger', + delay: 0 + }); + } + }); + + // Connected to Socket Successfully + Socket.on('connect', function () { + if (notifySocketError !== false) { + notifySocketError.close(); + notifySocketError = false; + } + }); + + Socket.on('error', function (err) { + console.error('There was an error while attemping to connect to the websocket: ' + err + '\n\nPlease try loading this page again.'); + }); + + Socket.on('live-stats', function (data) { + $.each(data.servers, function (uuid, info) { + var element = $('tr[data-server="' + uuid + '"]'); + switch (info.status) { + case 0: + element.find('[data-action="status"]').html('Offline'); + break; + case 1: + element.find('[data-action="status"]').html('Online'); + break; + case 2: + element.find('[data-action="status"]').html('Starting'); + break; + case 3: + element.find('[data-action="status"]').html('Stopping'); + break; + case 20: + element.find('[data-action="status"]').html('Installing'); + break; + case 30: + element.find('[data-action="status"]').html('Suspended'); + break; + } + if (info.status !== 0) { + var cpuMax = element.find('[data-action="cpu"]').data('cpumax'); + var currentCpu = info.proc.cpu.total; + if (cpuMax !== 0) { + currentCpu = parseFloat(((info.proc.cpu.total / cpuMax) * 100).toFixed(2).toString()); + } + element.find('[data-action="memory"]').html(parseInt(info.proc.memory.total / (1024 * 1024))); + element.find('[data-action="cpu"]').html(currentCpu); + } else { + element.find('[data-action="memory"]').html('--'); + element.find('[data-action="cpu"]').html('--'); + } + }); + }); +})(); diff --git a/public/themes/pterodactyl/js/frontend/2fa-modal.js b/public/themes/pterodactyl/js/frontend/2fa-modal.js index 935bd0f59..022ece2ff 100644 --- a/public/themes/pterodactyl/js/frontend/2fa-modal.js +++ b/public/themes/pterodactyl/js/frontend/2fa-modal.js @@ -37,7 +37,7 @@ var TwoFactorModal = (function () { }).done(function (data) { var image = new Image(); image.src = data.qrImage; - $(image).load(function () { + $(image).on('load', function () { $('#hide_img_load').slideUp(function () { $('#qr_image_insert').attr('src', image.src).slideDown(); }); @@ -85,7 +85,6 @@ var TwoFactorModal = (function () { bindListeners(); } } - -}); +})(); TwoFactorModal.init(); diff --git a/public/themes/pterodactyl/js/frontend/serverlist.js b/public/themes/pterodactyl/js/frontend/serverlist.js index 0490b7fbe..2a93a5a7c 100644 --- a/public/themes/pterodactyl/js/frontend/serverlist.js +++ b/public/themes/pterodactyl/js/frontend/serverlist.js @@ -17,16 +17,13 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. - -var ServerList = (function () { - +(function updateServerStatus() { var Status = { 0: 'Offline', 1: 'Online', 2: 'Starting', 3: 'Stopping' }; - $('.dynamic-update').each(function (index, data) { var element = $(this); var serverShortUUID = $(this).data('server'); @@ -81,20 +78,12 @@ var ServerList = (function () { element.find('[data-action="memory"]').html('--'); element.find('[data-action="cpu"]').html('--'); } - }).fail(function (jqXHR) { - console.error(jqXHR); - element.find('[data-action="status"]').html('Error'); - }); + } + }).fail(function (jqXHR) { + console.error(jqXHR); + element.find('[data-action="status"]').html('Error'); }); + }).promise().done(function () { setTimeout(updateServerStatus, 10000); - } - - return { - init: function () { - updateServerStatus(); - } - }; - + }); })(); - -ServerList.init(); diff --git a/public/themes/pterodactyl/vendor/fontawesome/animation.min.css b/public/themes/pterodactyl/vendor/fontawesome/animation.min.css new file mode 100644 index 000000000..8d4256393 --- /dev/null +++ b/public/themes/pterodactyl/vendor/fontawesome/animation.min.css @@ -0,0 +1,6 @@ +/*! + * font-awesome-animation - v0.0.9 + * https://github.com/l-lin/font-awesome-animation + * License: MIT + */ +@-webkit-keyframes wrench{0%{-webkit-transform:rotate(-12deg);transform:rotate(-12deg)}8%{-webkit-transform:rotate(12deg);transform:rotate(12deg)}10%{-webkit-transform:rotate(24deg);transform:rotate(24deg)}18%,20%{-webkit-transform:rotate(-24deg);transform:rotate(-24deg)}28%,30%{-webkit-transform:rotate(24deg);transform:rotate(24deg)}38%,40%{-webkit-transform:rotate(-24deg);transform:rotate(-24deg)}48%,50%{-webkit-transform:rotate(24deg);transform:rotate(24deg)}58%,60%{-webkit-transform:rotate(-24deg);transform:rotate(-24deg)}68%{-webkit-transform:rotate(24deg);transform:rotate(24deg)}100%,75%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}}@keyframes wrench{0%{-webkit-transform:rotate(-12deg);-ms-transform:rotate(-12deg);transform:rotate(-12deg)}8%{-webkit-transform:rotate(12deg);-ms-transform:rotate(12deg);transform:rotate(12deg)}10%{-webkit-transform:rotate(24deg);-ms-transform:rotate(24deg);transform:rotate(24deg)}18%,20%{-webkit-transform:rotate(-24deg);-ms-transform:rotate(-24deg);transform:rotate(-24deg)}28%,30%{-webkit-transform:rotate(24deg);-ms-transform:rotate(24deg);transform:rotate(24deg)}38%,40%{-webkit-transform:rotate(-24deg);-ms-transform:rotate(-24deg);transform:rotate(-24deg)}48%,50%{-webkit-transform:rotate(24deg);-ms-transform:rotate(24deg);transform:rotate(24deg)}58%,60%{-webkit-transform:rotate(-24deg);-ms-transform:rotate(-24deg);transform:rotate(-24deg)}68%{-webkit-transform:rotate(24deg);-ms-transform:rotate(24deg);transform:rotate(24deg)}100%,75%{-webkit-transform:rotate(0deg);-ms-transform:rotate(0deg);transform:rotate(0deg)}}.faa-parent.animated-hover:hover>.faa-wrench,.faa-wrench.animated,.faa-wrench.animated-hover:hover{-webkit-animation:wrench 2.5s ease infinite;animation:wrench 2.5s ease infinite;transform-origin-x:90%;transform-origin-y:35%;transform-origin-z:initial}.faa-parent.animated-hover:hover>.faa-wrench.faa-fast,.faa-wrench.animated-hover.faa-fast:hover,.faa-wrench.animated.faa-fast{-webkit-animation:wrench 1.2s ease infinite;animation:wrench 1.2s ease infinite}.faa-parent.animated-hover:hover>.faa-wrench.faa-slow,.faa-wrench.animated-hover.faa-slow:hover,.faa-wrench.animated.faa-slow{-webkit-animation:wrench 3.7s ease infinite;animation:wrench 3.7s ease infinite}@-webkit-keyframes ring{0%{-webkit-transform:rotate(-15deg);transform:rotate(-15deg)}2%{-webkit-transform:rotate(15deg);transform:rotate(15deg)}4%{-webkit-transform:rotate(-18deg);transform:rotate(-18deg)}6%{-webkit-transform:rotate(18deg);transform:rotate(18deg)}8%{-webkit-transform:rotate(-22deg);transform:rotate(-22deg)}10%{-webkit-transform:rotate(22deg);transform:rotate(22deg)}12%{-webkit-transform:rotate(-18deg);transform:rotate(-18deg)}14%{-webkit-transform:rotate(18deg);transform:rotate(18deg)}16%{-webkit-transform:rotate(-12deg);transform:rotate(-12deg)}18%{-webkit-transform:rotate(12deg);transform:rotate(12deg)}100%,20%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}}@keyframes ring{0%{-webkit-transform:rotate(-15deg);-ms-transform:rotate(-15deg);transform:rotate(-15deg)}2%{-webkit-transform:rotate(15deg);-ms-transform:rotate(15deg);transform:rotate(15deg)}4%{-webkit-transform:rotate(-18deg);-ms-transform:rotate(-18deg);transform:rotate(-18deg)}6%{-webkit-transform:rotate(18deg);-ms-transform:rotate(18deg);transform:rotate(18deg)}8%{-webkit-transform:rotate(-22deg);-ms-transform:rotate(-22deg);transform:rotate(-22deg)}10%{-webkit-transform:rotate(22deg);-ms-transform:rotate(22deg);transform:rotate(22deg)}12%{-webkit-transform:rotate(-18deg);-ms-transform:rotate(-18deg);transform:rotate(-18deg)}14%{-webkit-transform:rotate(18deg);-ms-transform:rotate(18deg);transform:rotate(18deg)}16%{-webkit-transform:rotate(-12deg);-ms-transform:rotate(-12deg);transform:rotate(-12deg)}18%{-webkit-transform:rotate(12deg);-ms-transform:rotate(12deg);transform:rotate(12deg)}100%,20%{-webkit-transform:rotate(0deg);-ms-transform:rotate(0deg);transform:rotate(0deg)}}.faa-parent.animated-hover:hover>.faa-ring,.faa-ring.animated,.faa-ring.animated-hover:hover{-webkit-animation:ring 2s ease infinite;animation:ring 2s ease infinite;transform-origin-x:50%;transform-origin-y:0;transform-origin-z:initial}.faa-parent.animated-hover:hover>.faa-ring.faa-fast,.faa-ring.animated-hover.faa-fast:hover,.faa-ring.animated.faa-fast{-webkit-animation:ring 1s ease infinite;animation:ring 1s ease infinite}.faa-parent.animated-hover:hover>.faa-ring.faa-slow,.faa-ring.animated-hover.faa-slow:hover,.faa-ring.animated.faa-slow{-webkit-animation:ring 3s ease infinite;animation:ring 3s ease infinite}@-webkit-keyframes vertical{0%{-webkit-transform:translate(0,-3px);transform:translate(0,-3px)}4%{-webkit-transform:translate(0,3px);transform:translate(0,3px)}8%{-webkit-transform:translate(0,-3px);transform:translate(0,-3px)}12%{-webkit-transform:translate(0,3px);transform:translate(0,3px)}16%{-webkit-transform:translate(0,-3px);transform:translate(0,-3px)}20%{-webkit-transform:translate(0,3px);transform:translate(0,3px)}100%,22%{-webkit-transform:translate(0,0);transform:translate(0,0)}}@keyframes vertical{0%{-webkit-transform:translate(0,-3px);-ms-transform:translate(0,-3px);transform:translate(0,-3px)}4%{-webkit-transform:translate(0,3px);-ms-transform:translate(0,3px);transform:translate(0,3px)}8%{-webkit-transform:translate(0,-3px);-ms-transform:translate(0,-3px);transform:translate(0,-3px)}12%{-webkit-transform:translate(0,3px);-ms-transform:translate(0,3px);transform:translate(0,3px)}16%{-webkit-transform:translate(0,-3px);-ms-transform:translate(0,-3px);transform:translate(0,-3px)}20%{-webkit-transform:translate(0,3px);-ms-transform:translate(0,3px);transform:translate(0,3px)}100%,22%{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}}.faa-parent.animated-hover:hover>.faa-vertical,.faa-vertical.animated,.faa-vertical.animated-hover:hover{-webkit-animation:vertical 2s ease infinite;animation:vertical 2s ease infinite}.faa-parent.animated-hover:hover>.faa-vertical.faa-fast,.faa-vertical.animated-hover.faa-fast:hover,.faa-vertical.animated.faa-fast{-webkit-animation:vertical 1s ease infinite;animation:vertical 1s ease infinite}.faa-parent.animated-hover:hover>.faa-vertical.faa-slow,.faa-vertical.animated-hover.faa-slow:hover,.faa-vertical.animated.faa-slow{-webkit-animation:vertical 4s ease infinite;animation:vertical 4s ease infinite}@-webkit-keyframes horizontal{0%{-webkit-transform:translate(0,0);transform:translate(0,0)}6%{-webkit-transform:translate(5px,0);transform:translate(5px,0)}12%{-webkit-transform:translate(0,0);transform:translate(0,0)}18%{-webkit-transform:translate(5px,0);transform:translate(5px,0)}24%{-webkit-transform:translate(0,0);transform:translate(0,0)}30%{-webkit-transform:translate(5px,0);transform:translate(5px,0)}100%,36%{-webkit-transform:translate(0,0);transform:translate(0,0)}}@keyframes horizontal{0%{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}6%{-webkit-transform:translate(5px,0);-ms-transform:translate(5px,0);transform:translate(5px,0)}12%{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}18%{-webkit-transform:translate(5px,0);-ms-transform:translate(5px,0);transform:translate(5px,0)}24%{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}30%{-webkit-transform:translate(5px,0);-ms-transform:translate(5px,0);transform:translate(5px,0)}100%,36%{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}}.faa-horizontal.animated,.faa-horizontal.animated-hover:hover,.faa-parent.animated-hover:hover>.faa-horizontal{-webkit-animation:horizontal 2s ease infinite;animation:horizontal 2s ease infinite}.faa-horizontal.animated-hover.faa-fast:hover,.faa-horizontal.animated.faa-fast,.faa-parent.animated-hover:hover>.faa-horizontal.faa-fast{-webkit-animation:horizontal 1s ease infinite;animation:horizontal 1s ease infinite}.faa-horizontal.animated-hover.faa-slow:hover,.faa-horizontal.animated.faa-slow,.faa-parent.animated-hover:hover>.faa-horizontal.faa-slow{-webkit-animation:horizontal 3s ease infinite;animation:horizontal 3s ease infinite}@-webkit-keyframes flash{0%,100%,50%{opacity:1}25%,75%{opacity:0}}@keyframes flash{0%,100%,50%{opacity:1}25%,75%{opacity:0}}.faa-flash.animated,.faa-flash.animated-hover:hover,.faa-parent.animated-hover:hover>.faa-flash{-webkit-animation:flash 2s ease infinite;animation:flash 2s ease infinite}.faa-flash.animated-hover.faa-fast:hover,.faa-flash.animated.faa-fast,.faa-parent.animated-hover:hover>.faa-flash.faa-fast{-webkit-animation:flash 1s ease infinite;animation:flash 1s ease infinite}.faa-flash.animated-hover.faa-slow:hover,.faa-flash.animated.faa-slow,.faa-parent.animated-hover:hover>.faa-flash.faa-slow{-webkit-animation:flash 3s ease infinite;animation:flash 3s ease infinite}@-webkit-keyframes bounce{0%,10%,100%,20%,50%,80%{-webkit-transform:translateY(0);transform:translateY(0)}40%,60%{-webkit-transform:translateY(-15px);transform:translateY(-15px)}}@keyframes bounce{0%,10%,100%,20%,50%,80%{-webkit-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}40%,60%{-webkit-transform:translateY(-15px);-ms-transform:translateY(-15px);transform:translateY(-15px)}}.faa-bounce.animated,.faa-bounce.animated-hover:hover,.faa-parent.animated-hover:hover>.faa-bounce{-webkit-animation:bounce 2s ease infinite;animation:bounce 2s ease infinite}.faa-bounce.animated-hover.faa-fast:hover,.faa-bounce.animated.faa-fast,.faa-parent.animated-hover:hover>.faa-bounce.faa-fast{-webkit-animation:bounce 1s ease infinite;animation:bounce 1s ease infinite}.faa-bounce.animated-hover.faa-slow:hover,.faa-bounce.animated.faa-slow,.faa-parent.animated-hover:hover>.faa-bounce.faa-slow{-webkit-animation:bounce 3s ease infinite;animation:bounce 3s ease infinite}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes spin{0%{-webkit-transform:rotate(0deg);-ms-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);-ms-transform:rotate(359deg);transform:rotate(359deg)}}.faa-parent.animated-hover:hover>.faa-spin,.faa-spin.animated,.faa-spin.animated-hover:hover{-webkit-animation:spin 1.5s linear infinite;animation:spin 1.5s linear infinite}.faa-parent.animated-hover:hover>.faa-spin.faa-fast,.faa-spin.animated-hover.faa-fast:hover,.faa-spin.animated.faa-fast{-webkit-animation:spin .7s linear infinite;animation:spin .7s linear infinite}.faa-parent.animated-hover:hover>.faa-spin.faa-slow,.faa-spin.animated-hover.faa-slow:hover,.faa-spin.animated.faa-slow{-webkit-animation:spin 2.2s linear infinite;animation:spin 2.2s linear infinite}@-webkit-keyframes float{0%{-webkit-transform:translateY(0);transform:translateY(0)}50%{-webkit-transform:translateY(-6px);transform:translateY(-6px)}100%{-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes float{0%{-webkit-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}50%{-webkit-transform:translateY(-6px);-ms-transform:translateY(-6px);transform:translateY(-6px)}100%{-webkit-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}.faa-float.animated,.faa-float.animated-hover:hover,.faa-parent.animated-hover:hover>.faa-float{-webkit-animation:float 2s linear infinite;animation:float 2s linear infinite}.faa-float.animated-hover.faa-fast:hover,.faa-float.animated.faa-fast,.faa-parent.animated-hover:hover>.faa-float.faa-fast{-webkit-animation:float 1s linear infinite;animation:float 1s linear infinite}.faa-float.animated-hover.faa-slow:hover,.faa-float.animated.faa-slow,.faa-parent.animated-hover:hover>.faa-float.faa-slow{-webkit-animation:float 3s linear infinite;animation:float 3s linear infinite}@-webkit-keyframes pulse{0%{-webkit-transform:scale(1.1);transform:scale(1.1)}50%{-webkit-transform:scale(0.8);transform:scale(0.8)}100%{-webkit-transform:scale(1.1);transform:scale(1.1)}}@keyframes pulse{0%{-webkit-transform:scale(1.1);-ms-transform:scale(1.1);transform:scale(1.1)}50%{-webkit-transform:scale(0.8);-ms-transform:scale(0.8);transform:scale(0.8)}100%{-webkit-transform:scale(1.1);-ms-transform:scale(1.1);transform:scale(1.1)}}.faa-parent.animated-hover:hover>.faa-pulse,.faa-pulse.animated,.faa-pulse.animated-hover:hover{-webkit-animation:pulse 2s linear infinite;animation:pulse 2s linear infinite}.faa-parent.animated-hover:hover>.faa-pulse.faa-fast,.faa-pulse.animated-hover.faa-fast:hover,.faa-pulse.animated.faa-fast{-webkit-animation:pulse 1s linear infinite;animation:pulse 1s linear infinite}.faa-parent.animated-hover:hover>.faa-pulse.faa-slow,.faa-pulse.animated-hover.faa-slow:hover,.faa-pulse.animated.faa-slow{-webkit-animation:pulse 3s linear infinite;animation:pulse 3s linear infinite}.faa-parent.animated-hover:hover>.faa-shake,.faa-shake.animated,.faa-shake.animated-hover:hover{-webkit-animation:wrench 2.5s ease infinite;animation:wrench 2.5s ease infinite}.faa-parent.animated-hover:hover>.faa-shake.faa-fast,.faa-shake.animated-hover.faa-fast:hover,.faa-shake.animated.faa-fast{-webkit-animation:wrench 1.2s ease infinite;animation:wrench 1.2s ease infinite}.faa-parent.animated-hover:hover>.faa-shake.faa-slow,.faa-shake.animated-hover.faa-slow:hover,.faa-shake.animated.faa-slow{-webkit-animation:wrench 3.7s ease infinite;animation:wrench 3.7s ease infinite}@-webkit-keyframes tada{0%{-webkit-transform:scale(1);transform:scale(1)}10%,20%{-webkit-transform:scale(.9) rotate(-8deg);transform:scale(.9) rotate(-8deg)}30%,50%,70%{-webkit-transform:scale(1.3) rotate(8deg);transform:scale(1.3) rotate(8deg)}40%,60%{-webkit-transform:scale(1.3) rotate(-8deg);transform:scale(1.3) rotate(-8deg)}100%,80%{-webkit-transform:scale(1) rotate(0);transform:scale(1) rotate(0)}}@keyframes tada{0%{-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}10%,20%{-webkit-transform:scale(.9) rotate(-8deg);-ms-transform:scale(.9) rotate(-8deg);transform:scale(.9) rotate(-8deg)}30%,50%,70%{-webkit-transform:scale(1.3) rotate(8deg);-ms-transform:scale(1.3) rotate(8deg);transform:scale(1.3) rotate(8deg)}40%,60%{-webkit-transform:scale(1.3) rotate(-8deg);-ms-transform:scale(1.3) rotate(-8deg);transform:scale(1.3) rotate(-8deg)}100%,80%{-webkit-transform:scale(1) rotate(0);-ms-transform:scale(1) rotate(0);transform:scale(1) rotate(0)}}.faa-parent.animated-hover:hover>.faa-tada,.faa-tada.animated,.faa-tada.animated-hover:hover{-webkit-animation:tada 2s linear infinite;animation:tada 2s linear infinite}.faa-parent.animated-hover:hover>.faa-tada.faa-fast,.faa-tada.animated-hover.faa-fast:hover,.faa-tada.animated.faa-fast{-webkit-animation:tada 1s linear infinite;animation:tada 1s linear infinite}.faa-parent.animated-hover:hover>.faa-tada.faa-slow,.faa-tada.animated-hover.faa-slow:hover,.faa-tada.animated.faa-slow{-webkit-animation:tada 3s linear infinite;animation:tada 3s linear infinite}@-webkit-keyframes passing{0%{-webkit-transform:translateX(-50%);transform:translateX(-50%);opacity:0}50%{-webkit-transform:translateX(0%);transform:translateX(0%);opacity:1}100%{-webkit-transform:translateX(50%);transform:translateX(50%);opacity:0}}@keyframes passing{0%{-webkit-transform:translateX(-50%);-ms-transform:translateX(-50%);transform:translateX(-50%);opacity:0}50%{-webkit-transform:translateX(0%);-ms-transform:translateX(0%);transform:translateX(0%);opacity:1}100%{-webkit-transform:translateX(50%);-ms-transform:translateX(50%);transform:translateX(50%);opacity:0}}.faa-parent.animated-hover:hover>.faa-passing,.faa-passing.animated,.faa-passing.animated-hover:hover{-webkit-animation:passing 2s linear infinite;animation:passing 2s linear infinite}.faa-parent.animated-hover:hover>.faa-passing.faa-fast,.faa-passing.animated-hover.faa-fast:hover,.faa-passing.animated.faa-fast{-webkit-animation:passing 1s linear infinite;animation:passing 1s linear infinite}.faa-parent.animated-hover:hover>.faa-passing.faa-slow,.faa-passing.animated-hover.faa-slow:hover,.faa-passing.animated.faa-slow{-webkit-animation:passing 3s linear infinite;animation:passing 3s linear infinite}@-webkit-keyframes passing-reverse{0%{-webkit-transform:translateX(50%);transform:translateX(50%);opacity:0}50%{-webkit-transform:translateX(0%);transform:translateX(0%);opacity:1}100%{-webkit-transform:translateX(-50%);transform:translateX(-50%);opacity:0}}@keyframes passing-reverse{0%{-webkit-transform:translateX(50%);-ms-transform:translateX(50%);transform:translateX(50%);opacity:0}50%{-webkit-transform:translateX(0%);-ms-transform:translateX(0%);transform:translateX(0%);opacity:1}100%{-webkit-transform:translateX(-50%);-ms-transform:translateX(-50%);transform:translateX(-50%);opacity:0}}.faa-parent.animated-hover:hover>.faa-passing-reverse,.faa-passing-reverse.animated,.faa-passing-reverse.animated-hover:hover{-webkit-animation:passing-reverse 2s linear infinite;animation:passing-reverse 2s linear infinite}.faa-parent.animated-hover:hover>.faa-passing-reverse.faa-fast,.faa-passing-reverse.animated-hover.faa-fast:hover,.faa-passing-reverse.animated.faa-fast{-webkit-animation:passing-reverse 1s linear infinite;animation:passing-reverse 1s linear infinite}.faa-parent.animated-hover:hover>.faa-passing-reverse.faa-slow,.faa-passing-reverse.animated-hover.faa-slow:hover,.faa-passing-reverse.animated.faa-slow{-webkit-animation:passing-reverse 3s linear infinite;animation:passing-reverse 3s linear infinite}@-webkit-keyframes burst{0%{opacity:.6}50%{-webkit-transform:scale(1.8);transform:scale(1.8);opacity:0}100%{opacity:0}}@keyframes burst{0%{opacity:.6}50%{-webkit-transform:scale(1.8);-ms-transform:scale(1.8);transform:scale(1.8);opacity:0}100%{opacity:0}}.faa-burst.animated,.faa-burst.animated-hover:hover,.faa-parent.animated-hover:hover>.faa-burst{-webkit-animation:burst 2s infinite linear;animation:burst 2s infinite linear}.faa-burst.animated-hover.faa-fast:hover,.faa-burst.animated.faa-fast,.faa-parent.animated-hover:hover>.faa-burst.faa-fast{-webkit-animation:burst 1s infinite linear;animation:burst 1s infinite linear}.faa-burst.animated-hover.faa-slow:hover,.faa-burst.animated.faa-slow,.faa-parent.animated-hover:hover>.faa-burst.faa-slow{-webkit-animation:burst 3s infinite linear;animation:burst 3s infinite linear} diff --git a/public/themes/pterodactyl/vendor/lodash/lodash.js b/public/themes/pterodactyl/vendor/lodash/lodash.js new file mode 100644 index 000000000..65083ba25 --- /dev/null +++ b/public/themes/pterodactyl/vendor/lodash/lodash.js @@ -0,0 +1,132 @@ +/** + * @license + * lodash lodash.com/license | Underscore.js 1.8.3 underscorejs.org/LICENSE + */ +;(function(){function t(t,n){return t.set(n[0],n[1]),t}function n(t,n){return t.add(n),t}function r(t,n,r){switch(r.length){case 0:return t.call(n);case 1:return t.call(n,r[0]);case 2:return t.call(n,r[0],r[1]);case 3:return t.call(n,r[0],r[1],r[2])}return t.apply(n,r)}function e(t,n,r,e){for(var u=-1,i=t?t.length:0;++u"']/g,Y=RegExp(G.source),H=RegExp(J.source),Q=/<%-([\s\S]+?)%>/g,X=/<%([\s\S]+?)%>/g,tt=/<%=([\s\S]+?)%>/g,nt=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,rt=/^\w*$/,et=/^\./,ut=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,it=/[\\^$.*+?()[\]{}|]/g,ot=RegExp(it.source),ft=/^\s+|\s+$/g,ct=/^\s+/,at=/\s+$/,lt=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,st=/\{\n\/\* \[wrapped with (.+)\] \*/,ht=/,? & /,pt=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g,_t=/\\(\\)?/g,vt=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,gt=/\w*$/,dt=/^[-+]0x[0-9a-f]+$/i,yt=/^0b[01]+$/i,bt=/^\[object .+?Constructor\]$/,xt=/^0o[0-7]+$/i,jt=/^(?:0|[1-9]\d*)$/,wt=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,mt=/($^)/,At=/['\n\r\u2028\u2029\\]/g,kt="[\\ufe0e\\ufe0f]?(?:[\\u0300-\\u036f\\ufe20-\\ufe23\\u20d0-\\u20f0]|\\ud83c[\\udffb-\\udfff])?(?:\\u200d(?:[^\\ud800-\\udfff]|(?:\\ud83c[\\udde6-\\uddff]){2}|[\\ud800-\\udbff][\\udc00-\\udfff])[\\ufe0e\\ufe0f]?(?:[\\u0300-\\u036f\\ufe20-\\ufe23\\u20d0-\\u20f0]|\\ud83c[\\udffb-\\udfff])?)*",Et="(?:[\\u2700-\\u27bf]|(?:\\ud83c[\\udde6-\\uddff]){2}|[\\ud800-\\udbff][\\udc00-\\udfff])"+kt,Ot="(?:[^\\ud800-\\udfff][\\u0300-\\u036f\\ufe20-\\ufe23\\u20d0-\\u20f0]?|[\\u0300-\\u036f\\ufe20-\\ufe23\\u20d0-\\u20f0]|(?:\\ud83c[\\udde6-\\uddff]){2}|[\\ud800-\\udbff][\\udc00-\\udfff]|[\\ud800-\\udfff])",St=RegExp("['\u2019]","g"),It=RegExp("[\\u0300-\\u036f\\ufe20-\\ufe23\\u20d0-\\u20f0]","g"),Rt=RegExp("\\ud83c[\\udffb-\\udfff](?=\\ud83c[\\udffb-\\udfff])|"+Ot+kt,"g"),zt=RegExp(["[A-Z\\xc0-\\xd6\\xd8-\\xde]?[a-z\\xdf-\\xf6\\xf8-\\xff]+(?:['\u2019](?:d|ll|m|re|s|t|ve))?(?=[\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000]|[A-Z\\xc0-\\xd6\\xd8-\\xde]|$)|(?:[A-Z\\xc0-\\xd6\\xd8-\\xde]|[^\\ud800-\\udfff\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000\\d+\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde])+(?:['\u2019](?:D|LL|M|RE|S|T|VE))?(?=[\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000]|[A-Z\\xc0-\\xd6\\xd8-\\xde](?:[a-z\\xdf-\\xf6\\xf8-\\xff]|[^\\ud800-\\udfff\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000\\d+\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde])|$)|[A-Z\\xc0-\\xd6\\xd8-\\xde]?(?:[a-z\\xdf-\\xf6\\xf8-\\xff]|[^\\ud800-\\udfff\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000\\d+\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde])+(?:['\u2019](?:d|ll|m|re|s|t|ve))?|[A-Z\\xc0-\\xd6\\xd8-\\xde]+(?:['\u2019](?:D|LL|M|RE|S|T|VE))?|\\d+",Et].join("|"),"g"),Wt=RegExp("[\\u200d\\ud800-\\udfff\\u0300-\\u036f\\ufe20-\\ufe23\\u20d0-\\u20f0\\ufe0e\\ufe0f]"),Bt=/[a-z][A-Z]|[A-Z]{2,}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/,Lt="Array Buffer DataView Date Error Float32Array Float64Array Function Int8Array Int16Array Int32Array Map Math Object Promise RegExp Set String Symbol TypeError Uint8Array Uint8ClampedArray Uint16Array Uint32Array WeakMap _ clearTimeout isFinite parseInt setTimeout".split(" "),Ut={}; +Ut["[object Float32Array]"]=Ut["[object Float64Array]"]=Ut["[object Int8Array]"]=Ut["[object Int16Array]"]=Ut["[object Int32Array]"]=Ut["[object Uint8Array]"]=Ut["[object Uint8ClampedArray]"]=Ut["[object Uint16Array]"]=Ut["[object Uint32Array]"]=true,Ut["[object Arguments]"]=Ut["[object Array]"]=Ut["[object ArrayBuffer]"]=Ut["[object Boolean]"]=Ut["[object DataView]"]=Ut["[object Date]"]=Ut["[object Error]"]=Ut["[object Function]"]=Ut["[object Map]"]=Ut["[object Number]"]=Ut["[object Object]"]=Ut["[object RegExp]"]=Ut["[object Set]"]=Ut["[object String]"]=Ut["[object WeakMap]"]=false; +var Ct={};Ct["[object Arguments]"]=Ct["[object Array]"]=Ct["[object ArrayBuffer]"]=Ct["[object DataView]"]=Ct["[object Boolean]"]=Ct["[object Date]"]=Ct["[object Float32Array]"]=Ct["[object Float64Array]"]=Ct["[object Int8Array]"]=Ct["[object Int16Array]"]=Ct["[object Int32Array]"]=Ct["[object Map]"]=Ct["[object Number]"]=Ct["[object Object]"]=Ct["[object RegExp]"]=Ct["[object Set]"]=Ct["[object String]"]=Ct["[object Symbol]"]=Ct["[object Uint8Array]"]=Ct["[object Uint8ClampedArray]"]=Ct["[object Uint16Array]"]=Ct["[object Uint32Array]"]=true, +Ct["[object Error]"]=Ct["[object Function]"]=Ct["[object WeakMap]"]=false;var Mt,Dt={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},Tt=parseFloat,$t=parseInt,Ft=typeof global=="object"&&global&&global.Object===Object&&global,Nt=typeof self=="object"&&self&&self.Object===Object&&self,Pt=Ft||Nt||Function("return this")(),Zt=typeof exports=="object"&&exports&&!exports.nodeType&&exports,qt=Zt&&typeof module=="object"&&module&&!module.nodeType&&module,Vt=qt&&qt.exports===Zt,Kt=Vt&&Ft.h; +t:{try{Mt=Kt&&Kt.g("util");break t}catch(t){}Mt=void 0}var Gt=Mt&&Mt.isArrayBuffer,Jt=Mt&&Mt.isDate,Yt=Mt&&Mt.isMap,Ht=Mt&&Mt.isRegExp,Qt=Mt&&Mt.isSet,Xt=Mt&&Mt.isTypedArray,tn=j("length"),nn=w({"\xc0":"A","\xc1":"A","\xc2":"A","\xc3":"A","\xc4":"A","\xc5":"A","\xe0":"a","\xe1":"a","\xe2":"a","\xe3":"a","\xe4":"a","\xe5":"a","\xc7":"C","\xe7":"c","\xd0":"D","\xf0":"d","\xc8":"E","\xc9":"E","\xca":"E","\xcb":"E","\xe8":"e","\xe9":"e","\xea":"e","\xeb":"e","\xcc":"I","\xcd":"I","\xce":"I","\xcf":"I", +"\xec":"i","\xed":"i","\xee":"i","\xef":"i","\xd1":"N","\xf1":"n","\xd2":"O","\xd3":"O","\xd4":"O","\xd5":"O","\xd6":"O","\xd8":"O","\xf2":"o","\xf3":"o","\xf4":"o","\xf5":"o","\xf6":"o","\xf8":"o","\xd9":"U","\xda":"U","\xdb":"U","\xdc":"U","\xf9":"u","\xfa":"u","\xfb":"u","\xfc":"u","\xdd":"Y","\xfd":"y","\xff":"y","\xc6":"Ae","\xe6":"ae","\xde":"Th","\xfe":"th","\xdf":"ss","\u0100":"A","\u0102":"A","\u0104":"A","\u0101":"a","\u0103":"a","\u0105":"a","\u0106":"C","\u0108":"C","\u010a":"C","\u010c":"C", +"\u0107":"c","\u0109":"c","\u010b":"c","\u010d":"c","\u010e":"D","\u0110":"D","\u010f":"d","\u0111":"d","\u0112":"E","\u0114":"E","\u0116":"E","\u0118":"E","\u011a":"E","\u0113":"e","\u0115":"e","\u0117":"e","\u0119":"e","\u011b":"e","\u011c":"G","\u011e":"G","\u0120":"G","\u0122":"G","\u011d":"g","\u011f":"g","\u0121":"g","\u0123":"g","\u0124":"H","\u0126":"H","\u0125":"h","\u0127":"h","\u0128":"I","\u012a":"I","\u012c":"I","\u012e":"I","\u0130":"I","\u0129":"i","\u012b":"i","\u012d":"i","\u012f":"i", +"\u0131":"i","\u0134":"J","\u0135":"j","\u0136":"K","\u0137":"k","\u0138":"k","\u0139":"L","\u013b":"L","\u013d":"L","\u013f":"L","\u0141":"L","\u013a":"l","\u013c":"l","\u013e":"l","\u0140":"l","\u0142":"l","\u0143":"N","\u0145":"N","\u0147":"N","\u014a":"N","\u0144":"n","\u0146":"n","\u0148":"n","\u014b":"n","\u014c":"O","\u014e":"O","\u0150":"O","\u014d":"o","\u014f":"o","\u0151":"o","\u0154":"R","\u0156":"R","\u0158":"R","\u0155":"r","\u0157":"r","\u0159":"r","\u015a":"S","\u015c":"S","\u015e":"S", +"\u0160":"S","\u015b":"s","\u015d":"s","\u015f":"s","\u0161":"s","\u0162":"T","\u0164":"T","\u0166":"T","\u0163":"t","\u0165":"t","\u0167":"t","\u0168":"U","\u016a":"U","\u016c":"U","\u016e":"U","\u0170":"U","\u0172":"U","\u0169":"u","\u016b":"u","\u016d":"u","\u016f":"u","\u0171":"u","\u0173":"u","\u0174":"W","\u0175":"w","\u0176":"Y","\u0177":"y","\u0178":"Y","\u0179":"Z","\u017b":"Z","\u017d":"Z","\u017a":"z","\u017c":"z","\u017e":"z","\u0132":"IJ","\u0133":"ij","\u0152":"Oe","\u0153":"oe","\u0149":"'n", +"\u017f":"s"}),rn=w({"&":"&","<":"<",">":">",'"':""","'":"'"}),en=w({"&":"&","<":"<",">":">",""":'"',"'":"'"}),un=function w(kt){function Et(t){return oi.call(t)}function Ot(t){if(_u(t)&&!tf(t)&&!(t instanceof Dt)){if(t instanceof Mt)return t;if(ei.call(t,"__wrapped__"))return Ce(t)}return new Mt(t)}function Rt(){}function Mt(t,n){this.__wrapped__=t,this.__actions__=[],this.__chain__=!!n,this.__index__=0,this.__values__=F}function Dt(t){this.__wrapped__=t,this.__actions__=[], +this.__dir__=1,this.__filtered__=false,this.__iteratees__=[],this.__takeCount__=4294967295,this.__views__=[]}function Ft(t){var n=-1,r=t?t.length:0;for(this.clear();++n=n?t:n)),t}function yn(t,n,r,e,i,o,f){var c;if(e&&(c=o?e(t,i,o,f):e(t)), +c!==F)return c;if(!pu(t))return t;if(i=tf(t)){if(c=de(t),!n)return Ur(t,c)}else{var a=Et(t),l="[object Function]"==a||"[object GeneratorFunction]"==a;if(rf(t))return Rr(t,n);if("[object Object]"==a||"[object Arguments]"==a||l&&!o){if(c=ye(l?{}:t),!n)return Mr(t,_n(c,t))}else{if(!Ct[a])return o?t:{};c=be(t,a,yn,n)}}if(f||(f=new Kt),o=f.get(t))return o;if(f.set(t,c),!i)var s=r?zn(t,Su,ao):Su(t);return u(s||t,function(u,i){s&&(i=u,u=t[i]),sn(c,i,yn(u,n,r,e,i,t,f))}),c}function bn(t){var n=Su(t);return function(r){ +return xn(r,t,n)}}function xn(t,n,r){var e=r.length;if(null==t)return!e;for(t=Gu(t);e--;){var u=r[e],i=n[u],o=t[u];if(o===F&&!(u in t)||!i(o))return false}return true}function jn(t,n,r){if(typeof t!="function")throw new Hu("Expected a function");return po(function(){t.apply(F,r)},n)}function wn(t,n,r,e){var u=-1,i=c,o=true,f=t.length,s=[],h=n.length;if(!f)return s;r&&(n=l(n,S(r))),e?(i=a,o=false):200<=n.length&&(i=R,o=false,n=new qt(n));t:for(;++un}function Bn(t,n){return null!=t&&ei.call(t,n)}function Ln(t,n){return null!=t&&n in Gu(t)}function Un(t,n,r){for(var e=r?a:c,u=t[0].length,i=t.length,o=i,f=Pu(i),s=1/0,h=[];o--;){var p=t[o];o&&n&&(p=l(p,S(n))),s=zi(p.length,s), +f[o]=!r&&(n||120<=u&&120<=p.length)?new qt(o&&p):F}var p=t[0],_=-1,v=f[0];t:for(;++_n?r:0,je(n,r)?t[n]:F}function rr(t,n,r){var e=-1;return n=l(n.length?n:[Cu],S(he())),t=Hn(t,function(t){return{a:l(n,function(n){return n(t)}),b:++e,c:t +}}),A(t,function(t,n){var e;t:{e=-1;for(var u=t.a,i=n.a,o=u.length,f=r.length;++e=f?c:c*("desc"==r[e]?-1:1);break t}}e=t.b-n.b}return e})}function er(t,n){return t=Gu(t),ur(t,n,function(n,r){return r in t})}function ur(t,n,r){for(var e=-1,u=n.length,i={};++en||9007199254740991n&&(n=-n>u?0:u+n),r=r>u?u:r,0>r&&(r+=u),u=n>r?0:r-n>>>0,n>>>=0,r=Pu(u);++e=u){for(;e>>1,o=t[i];null!==o&&!yu(o)&&(r?o<=n:o=e?t:vr(t,n,r)}function Rr(t,n){if(n)return t.slice();var r=t.length,r=hi?hi(r):new t.constructor(r);return t.copy(r),r}function zr(t){var n=new t.constructor(t.byteLength);return new si(n).set(new si(t)),n}function Wr(t,n){if(t!==n){var r=t!==F,e=null===t,u=t===t,i=yu(t),o=n!==F,f=null===n,c=n===n,a=yu(n);if(!f&&!a&&!i&&t>n||i&&o&&c&&!f&&!a||e&&o&&c||!r&&c||!u)return 1;if(!e&&!i&&!a&&tu?F:i,u=1),n=Gu(n);++eo&&f[0]!==a&&f[o-1]!==a?[]:C(f,a),o-=c.length,or?r?ar(n,t):n:(r=ar(n,mi(t/T(n))),Wt.test(n)?Ir($(r),0,t).join(""):r.slice(0,t)); +}function te(t,n,e,u){function i(){for(var n=-1,c=arguments.length,a=-1,l=u.length,s=Pu(l+c),h=this&&this!==Pt&&this instanceof i?f:t;++an||e)&&(1&t&&(i[2]=h[2],n|=1&r?0:4),(r=h[3])&&(e=i[3],i[3]=e?Br(e,r,h[4]):r,i[4]=e?C(i[3],"__lodash_placeholder__"):h[4]), +(r=h[5])&&(e=i[5],i[5]=e?Lr(e,r,h[6]):r,i[6]=e?C(i[5],"__lodash_placeholder__"):h[6]),(r=h[7])&&(i[7]=r),128&t&&(i[8]=null==i[8]?h[8]:zi(i[8],h[8])),null==i[9]&&(i[9]=h[9]),i[0]=h[0],i[1]=n),t=i[0],n=i[1],r=i[2],e=i[3],u=i[4],f=i[9]=null==i[9]?c?0:t.length:Ri(i[9]-a,0),!f&&24&n&&(n&=-25),Re((h?uo:ho)(n&&1!=n?8==n||16==n?Vr(t,n,f):32!=n&&33!=n||u.length?Jr.apply(F,i):te(t,n,r,e):Nr(t,n,r),i),t,n)}function fe(t,n,r,e,u,i){var o=2&u,f=t.length,c=n.length;if(f!=c&&!(o&&c>f))return false;if((c=i.get(t))&&i.get(n))return c==n; +var c=-1,a=true,l=1&u?new qt:F;for(i.set(t,n),i.set(n,t);++cr&&(r=Ri(e+r,0)),g(t,he(n,3),r)):-1}function De(t,n,r){var e=t?t.length:0;if(!e)return-1;var u=e-1;return r!==F&&(u=ju(r),u=0>r?Ri(e+u,0):zi(u,e-1)),g(t,he(n,3),u,true)}function Te(t){return t&&t.length?En(t,1):[]}function $e(t){return t&&t.length?t[0]:F}function Fe(t){var n=t?t.length:0;return n?t[n-1]:F}function Ne(t,n){return t&&t.length&&n&&n.length?or(t,n):t}function Pe(t){return t?Ui.call(t):t}function Ze(t){if(!t||!t.length)return[];var n=0;return t=f(t,function(t){if(cu(t))return n=Ri(t.length,n), +!0}),E(n,function(n){return l(t,j(n))})}function qe(t,n){if(!t||!t.length)return[];var e=Ze(t);return null==n?e:l(e,function(t){return r(n,F,t)})}function Ve(t){return t=Ot(t),t.__chain__=true,t}function Ke(t,n){return n(t)}function Ge(){return this}function Je(t,n){return(tf(t)?u:to)(t,he(n,3))}function Ye(t,n){return(tf(t)?i:no)(t,he(n,3))}function He(t,n){return(tf(t)?l:Hn)(t,he(n,3))}function Qe(t,n,r){return n=r?F:n,n=t&&null==n?t.length:n,oe(t,128,F,F,F,F,n)}function Xe(t,n){var r;if(typeof n!="function")throw new Hu("Expected a function"); +return t=ju(t),function(){return 0<--t&&(r=n.apply(this,arguments)),1>=t&&(n=F),r}}function tu(t,n,r){return n=r?F:n,t=oe(t,8,F,F,F,F,F,n),t.placeholder=tu.placeholder,t}function nu(t,n,r){return n=r?F:n,t=oe(t,16,F,F,F,F,F,n),t.placeholder=nu.placeholder,t}function ru(t,n,r){function e(n){var r=c,e=a;return c=a=F,_=n,s=t.apply(e,r)}function u(t){var r=t-p;return t-=_,p===F||r>=n||0>r||g&&t>=l}function i(){var t=Po();if(u(t))return o(t);var r,e=po;r=t-_,t=n-(t-p),r=g?zi(t,l-r):t,h=e(i,r)}function o(t){ +return h=F,d&&c?e(t):(c=a=F,s)}function f(){var t=Po(),r=u(t);if(c=arguments,a=this,p=t,r){if(h===F)return _=t=p,h=po(i,n),v?e(t):s;if(g)return h=po(i,n),e(p)}return h===F&&(h=po(i,n)),s}var c,a,l,s,h,p,_=0,v=false,g=false,d=true;if(typeof t!="function")throw new Hu("Expected a function");return n=mu(n)||0,pu(r)&&(v=!!r.leading,l=(g="maxWait"in r)?Ri(mu(r.maxWait)||0,n):l,d="trailing"in r?!!r.trailing:d),f.cancel=function(){h!==F&&oo(h),_=0,c=p=a=h=F},f.flush=function(){return h===F?s:o(Po())},f}function eu(t,n){ +function r(){var e=arguments,u=n?n.apply(this,e):e[0],i=r.cache;return i.has(u)?i.get(u):(e=t.apply(this,e),r.cache=i.set(u,e)||i,e)}if(typeof t!="function"||n&&typeof n!="function")throw new Hu("Expected a function");return r.cache=new(eu.Cache||Zt),r}function uu(t){if(typeof t!="function")throw new Hu("Expected a function");return function(){var n=arguments;switch(n.length){case 0:return!t.call(this);case 1:return!t.call(this,n[0]);case 2:return!t.call(this,n[0],n[1]);case 3:return!t.call(this,n[0],n[1],n[2]); +}return!t.apply(this,n)}}function iu(t,n){return t===n||t!==t&&n!==n}function ou(t){return cu(t)&&ei.call(t,"callee")&&(!di.call(t,"callee")||"[object Arguments]"==oi.call(t))}function fu(t){return null!=t&&hu(t.length)&&!lu(t)}function cu(t){return _u(t)&&fu(t)}function au(t){return!!_u(t)&&("[object Error]"==oi.call(t)||typeof t.message=="string"&&typeof t.name=="string")}function lu(t){return t=pu(t)?oi.call(t):"","[object Function]"==t||"[object GeneratorFunction]"==t}function su(t){return typeof t=="number"&&t==ju(t); +}function hu(t){return typeof t=="number"&&-1=t}function pu(t){var n=typeof t;return null!=t&&("object"==n||"function"==n)}function _u(t){return null!=t&&typeof t=="object"}function vu(t){return typeof t=="number"||_u(t)&&"[object Number]"==oi.call(t)}function gu(t){return!(!_u(t)||"[object Object]"!=oi.call(t))&&(t=_i(t),null===t||(t=ei.call(t,"constructor")&&t.constructor,typeof t=="function"&&t instanceof t&&ri.call(t)==ii))}function du(t){return typeof t=="string"||!tf(t)&&_u(t)&&"[object String]"==oi.call(t); +}function yu(t){return typeof t=="symbol"||_u(t)&&"[object Symbol]"==oi.call(t)}function bu(t){if(!t)return[];if(fu(t))return du(t)?$(t):Ur(t);if(vi&&t[vi]){t=t[vi]();for(var n,r=[];!(n=t.next()).done;)r.push(n.value);return r}return n=Et(t),("[object Map]"==n?L:"[object Set]"==n?M:zu)(t)}function xu(t){return t?(t=mu(t),t===N||t===-N?1.7976931348623157e308*(0>t?-1:1):t===t?t:0):0===t?t:0}function ju(t){t=xu(t);var n=t%1;return t===t?n?t-n:t:0}function wu(t){return t?dn(ju(t),0,4294967295):0}function mu(t){ +if(typeof t=="number")return t;if(yu(t))return P;if(pu(t)&&(t=typeof t.valueOf=="function"?t.valueOf():t,t=pu(t)?t+"":t),typeof t!="string")return 0===t?t:+t;t=t.replace(ft,"");var n=yt.test(t);return n||xt.test(t)?$t(t.slice(2),n?2:8):dt.test(t)?P:+t}function Au(t){return Cr(t,Iu(t))}function ku(t){return null==t?"":jr(t)}function Eu(t,n,r){return t=null==t?F:Rn(t,n),t===F?r:t}function Ou(t,n){return null!=t&&ge(t,n,Ln)}function Su(t){return fu(t)?tn(t):Gn(t)}function Iu(t){return fu(t)?tn(t,true):Jn(t); +}function Ru(t,n){return null==t?{}:ur(t,zn(t,Iu,lo),he(n))}function zu(t){return t?I(t,Su(t)):[]}function Wu(t){return Lf(ku(t).toLowerCase())}function Bu(t){return(t=ku(t))&&t.replace(wt,nn).replace(It,"")}function Lu(t,n,r){return t=ku(t),n=r?F:n,n===F?Bt.test(t)?t.match(zt)||[]:t.match(pt)||[]:t.match(n)||[]}function Uu(t){return function(){return t}}function Cu(t){return t}function Mu(t){return Kn(typeof t=="function"?t:yn(t,true))}function Du(t,n,r){var e=Su(n),i=In(n,e);null!=r||pu(n)&&(i.length||!e.length)||(r=n, +n=t,t=this,i=In(n,Su(n)));var o=!(pu(r)&&"chain"in r&&!r.chain),f=lu(t);return u(i,function(r){var e=n[r];t[r]=e,f&&(t.prototype[r]=function(){var n=this.__chain__;if(o||n){var r=t(this.__wrapped__);return(r.__actions__=Ur(this.__actions__)).push({func:e,args:arguments,thisArg:t}),r.__chain__=n,r}return e.apply(t,s([this.value()],arguments))})}),t}function Tu(){}function $u(t){return me(t)?j(Be(t)):ir(t)}function Fu(){return[]}function Nu(){return false}kt=kt?un.defaults(Pt.Object(),kt,un.pick(Pt,Lt)):Pt; +var Pu=kt.Array,Zu=kt.Date,qu=kt.Error,Vu=kt.Function,Ku=kt.Math,Gu=kt.Object,Ju=kt.RegExp,Yu=kt.String,Hu=kt.TypeError,Qu=Pu.prototype,Xu=Gu.prototype,ti=kt["__core-js_shared__"],ni=function(){var t=/[^.]+$/.exec(ti&&ti.keys&&ti.keys.IE_PROTO||"");return t?"Symbol(src)_1."+t:""}(),ri=Vu.prototype.toString,ei=Xu.hasOwnProperty,ui=0,ii=ri.call(Gu),oi=Xu.toString,fi=Pt._,ci=Ju("^"+ri.call(ei).replace(it,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),ai=Vt?kt.Buffer:F,li=kt.Symbol,si=kt.Uint8Array,hi=ai?ai.f:F,pi=Gu.defineProperty,_i=U(Gu.getPrototypeOf,Gu),vi=li?li.iterator:F,gi=Gu.create,di=Xu.propertyIsEnumerable,yi=Qu.splice,bi=li?li.isConcatSpreadable:F,xi=kt.clearTimeout!==Pt.clearTimeout&&kt.clearTimeout,ji=Zu&&Zu.now!==Pt.Date.now&&Zu.now,wi=kt.setTimeout!==Pt.setTimeout&&kt.setTimeout,mi=Ku.ceil,Ai=Ku.floor,ki=Gu.getOwnPropertySymbols,Ei=ai?ai.isBuffer:F,Oi=kt.isFinite,Si=Qu.join,Ii=U(Gu.keys,Gu),Ri=Ku.max,zi=Ku.min,Wi=Zu.now,Bi=kt.parseInt,Li=Ku.random,Ui=Qu.reverse,Ci=ve(kt,"DataView"),Mi=ve(kt,"Map"),Di=ve(kt,"Promise"),Ti=ve(kt,"Set"),$i=ve(kt,"WeakMap"),Fi=ve(Gu,"create"),Ni=ve(Gu,"defineProperty"),Pi=$i&&new $i,Zi={},qi=Le(Ci),Vi=Le(Mi),Ki=Le(Di),Gi=Le(Ti),Ji=Le($i),Yi=li?li.prototype:F,Hi=Yi?Yi.valueOf:F,Qi=Yi?Yi.toString:F,Xi=function(){ +function t(){}return function(n){return pu(n)?gi?gi(n):(t.prototype=prototype,n=new t,t.prototype=F,n):{}}}();Ot.templateSettings={escape:Q,evaluate:X,interpolate:tt,variable:"",imports:{_:Ot}},Ot.prototype=Rt.prototype,Ot.prototype.constructor=Ot,Mt.prototype=Xi(Rt.prototype),Mt.prototype.constructor=Mt,Dt.prototype=Xi(Rt.prototype),Dt.prototype.constructor=Dt,Ft.prototype.clear=function(){this.__data__=Fi?Fi(null):{},this.size=0},Ft.prototype.delete=function(t){return t=this.has(t)&&delete this.__data__[t], +this.size-=t?1:0,t},Ft.prototype.get=function(t){var n=this.__data__;return Fi?(t=n[t],"__lodash_hash_undefined__"===t?F:t):ei.call(n,t)?n[t]:F},Ft.prototype.has=function(t){var n=this.__data__;return Fi?n[t]!==F:ei.call(n,t)},Ft.prototype.set=function(t,n){var r=this.__data__;return this.size+=this.has(t)?0:1,r[t]=Fi&&n===F?"__lodash_hash_undefined__":n,this},Nt.prototype.clear=function(){this.__data__=[],this.size=0},Nt.prototype.delete=function(t){var n=this.__data__;return t=hn(n,t),!(0>t)&&(t==n.length-1?n.pop():yi.call(n,t,1), +--this.size,true)},Nt.prototype.get=function(t){var n=this.__data__;return t=hn(n,t),0>t?F:n[t][1]},Nt.prototype.has=function(t){return-1e?(++this.size,r.push([t,n])):r[e][1]=n,this},Zt.prototype.clear=function(){this.size=0,this.__data__={hash:new Ft,map:new(Mi||Nt),string:new Ft}},Zt.prototype.delete=function(t){return t=pe(this,t).delete(t),this.size-=t?1:0,t},Zt.prototype.get=function(t){return pe(this,t).get(t); +},Zt.prototype.has=function(t){return pe(this,t).has(t)},Zt.prototype.set=function(t,n){var r=pe(this,t),e=r.size;return r.set(t,n),this.size+=r.size==e?0:1,this},qt.prototype.add=qt.prototype.push=function(t){return this.__data__.set(t,"__lodash_hash_undefined__"),this},qt.prototype.has=function(t){return this.__data__.has(t)},Kt.prototype.clear=function(){this.__data__=new Nt,this.size=0},Kt.prototype.delete=function(t){var n=this.__data__;return t=n.delete(t),this.size=n.size,t},Kt.prototype.get=function(t){ +return this.__data__.get(t)},Kt.prototype.has=function(t){return this.__data__.has(t)},Kt.prototype.set=function(t,n){var r=this.__data__;if(r instanceof Nt){var e=r.__data__;if(!Mi||199>e.length)return e.push([t,n]),this.size=++r.size,this;r=this.__data__=new Zt(e)}return r.set(t,n),this.size=r.size,this};var to=$r(On),no=$r(Sn,true),ro=Fr(),eo=Fr(true),uo=Pi?function(t,n){return Pi.set(t,n),t}:Cu,io=Ni?function(t,n){return Ni(t,"toString",{configurable:true,enumerable:false,value:Uu(n),writable:true})}:Cu,oo=xi||function(t){ +return Pt.clearTimeout(t)},fo=Ti&&1/M(new Ti([,-0]))[1]==N?function(t){return new Ti(t)}:Tu,co=Pi?function(t){return Pi.get(t)}:Tu,ao=ki?U(ki,Gu):Fu,lo=ki?function(t){for(var n=[];t;)s(n,ao(t)),t=_i(t);return n}:Fu;(Ci&&"[object DataView]"!=Et(new Ci(new ArrayBuffer(1)))||Mi&&"[object Map]"!=Et(new Mi)||Di&&"[object Promise]"!=Et(Di.resolve())||Ti&&"[object Set]"!=Et(new Ti)||$i&&"[object WeakMap]"!=Et(new $i))&&(Et=function(t){var n=oi.call(t);if(t=(t="[object Object]"==n?t.constructor:F)?Le(t):F)switch(t){ +case qi:return"[object DataView]";case Vi:return"[object Map]";case Ki:return"[object Promise]";case Gi:return"[object Set]";case Ji:return"[object WeakMap]"}return n});var so=ti?lu:Nu,ho=ze(uo),po=wi||function(t,n){return Pt.setTimeout(t,n)},_o=ze(io),vo=function(t){t=eu(t,function(t){return 500===n.size&&n.clear(),t});var n=t.cache;return t}(function(t){t=ku(t);var n=[];return et.test(t)&&n.push(""),t.replace(ut,function(t,r,e,u){n.push(e?u.replace(_t,"$1"):r||t)}),n}),go=lr(function(t,n){return cu(t)?wn(t,En(n,1,cu,true)):[]; +}),yo=lr(function(t,n){var r=Fe(n);return cu(r)&&(r=F),cu(t)?wn(t,En(n,1,cu,true),he(r,2)):[]}),bo=lr(function(t,n){var r=Fe(n);return cu(r)&&(r=F),cu(t)?wn(t,En(n,1,cu,true),F,r):[]}),xo=lr(function(t){var n=l(t,Or);return n.length&&n[0]===t[0]?Un(n):[]}),jo=lr(function(t){var n=Fe(t),r=l(t,Or);return n===Fe(r)?n=F:r.pop(),r.length&&r[0]===t[0]?Un(r,he(n,2)):[]}),wo=lr(function(t){var n=Fe(t),r=l(t,Or);return n===Fe(r)?n=F:r.pop(),r.length&&r[0]===t[0]?Un(r,F,n):[]}),mo=lr(Ne),Ao=ae(function(t,n){var r=t?t.length:0,e=gn(t,n); +return fr(t,l(n,function(t){return je(t,r)?+t:t}).sort(Wr)),e}),ko=lr(function(t){return wr(En(t,1,cu,true))}),Eo=lr(function(t){var n=Fe(t);return cu(n)&&(n=F),wr(En(t,1,cu,true),he(n,2))}),Oo=lr(function(t){var n=Fe(t);return cu(n)&&(n=F),wr(En(t,1,cu,true),F,n)}),So=lr(function(t,n){return cu(t)?wn(t,n):[]}),Io=lr(function(t){return kr(f(t,cu))}),Ro=lr(function(t){var n=Fe(t);return cu(n)&&(n=F),kr(f(t,cu),he(n,2))}),zo=lr(function(t){var n=Fe(t);return cu(n)&&(n=F),kr(f(t,cu),F,n)}),Wo=lr(Ze),Bo=lr(function(t){ +var n=t.length,n=1=n}),tf=Pu.isArray,nf=Gt?S(Gt):Dn,rf=Ei||Nu,ef=Jt?S(Jt):Tn,uf=Yt?S(Yt):Fn,of=Ht?S(Ht):Zn,ff=Qt?S(Qt):qn,cf=Xt?S(Xt):Vn,af=re(Yn),lf=re(function(t,n){return t<=n}),sf=Tr(function(t,n){if(ke(n)||fu(n))Cr(n,Su(n),t);else for(var r in n)ei.call(n,r)&&sn(t,r,n[r])}),hf=Tr(function(t,n){Cr(n,Iu(n),t)}),pf=Tr(function(t,n,r,e){Cr(n,Iu(n),t,e)}),_f=Tr(function(t,n,r,e){ +Cr(n,Su(n),t,e)}),vf=ae(gn),gf=lr(function(t){return t.push(F,an),r(pf,F,t)}),df=lr(function(t){return t.push(F,Oe),r(wf,F,t)}),yf=Yr(function(t,n,r){t[n]=r},Uu(Cu)),bf=Yr(function(t,n,r){ei.call(t,n)?t[n].push(r):t[n]=[r]},he),xf=lr(Mn),jf=Tr(function(t,n,r){tr(t,n,r)}),wf=Tr(function(t,n,r,e){tr(t,n,r,e)}),mf=ae(function(t,n){return null==t?{}:(n=l(n,Be),er(t,wn(zn(t,Iu,lo),n)))}),Af=ae(function(t,n){return null==t?{}:er(t,l(n,Be))}),kf=ie(Su),Ef=ie(Iu),Of=Zr(function(t,n,r){return n=n.toLowerCase(), +t+(r?Wu(n):n)}),Sf=Zr(function(t,n,r){return t+(r?"-":"")+n.toLowerCase()}),If=Zr(function(t,n,r){return t+(r?" ":"")+n.toLowerCase()}),Rf=Pr("toLowerCase"),zf=Zr(function(t,n,r){return t+(r?"_":"")+n.toLowerCase()}),Wf=Zr(function(t,n,r){return t+(r?" ":"")+Lf(n)}),Bf=Zr(function(t,n,r){return t+(r?" ":"")+n.toUpperCase()}),Lf=Pr("toUpperCase"),Uf=lr(function(t,n){try{return r(t,F,n)}catch(t){return au(t)?t:new qu(t)}}),Cf=ae(function(t,n){return u(n,function(n){n=Be(n),vn(t,n,Zo(t[n],t))}),t}),Mf=Gr(),Df=Gr(true),Tf=lr(function(t,n){ +return function(r){return Mn(r,t,n)}}),$f=lr(function(t,n){return function(r){return Mn(t,r,n)}}),Ff=Qr(l),Nf=Qr(o),Pf=Qr(_),Zf=ne(),qf=ne(true),Vf=Hr(function(t,n){return t+n},0),Kf=ue("ceil"),Gf=Hr(function(t,n){return t/n},1),Jf=ue("floor"),Yf=Hr(function(t,n){return t*n},1),Hf=ue("round"),Qf=Hr(function(t,n){return t-n},0);return Ot.after=function(t,n){if(typeof n!="function")throw new Hu("Expected a function");return t=ju(t),function(){if(1>--t)return n.apply(this,arguments)}},Ot.ary=Qe,Ot.assign=sf, +Ot.assignIn=hf,Ot.assignInWith=pf,Ot.assignWith=_f,Ot.at=vf,Ot.before=Xe,Ot.bind=Zo,Ot.bindAll=Cf,Ot.bindKey=qo,Ot.castArray=function(){if(!arguments.length)return[];var t=arguments[0];return tf(t)?t:[t]},Ot.chain=Ve,Ot.chunk=function(t,n,r){if(n=(r?we(t,n,r):n===F)?1:Ri(ju(n),0),r=t?t.length:0,!r||1>n)return[];for(var e=0,u=0,i=Pu(mi(r/n));en?0:n,e)):[]},Ot.dropRight=function(t,n,r){var e=t?t.length:0;return e?(n=r||n===F?1:ju(n),n=e-n,vr(t,0,0>n?0:n)):[]},Ot.dropRightWhile=function(t,n){return t&&t.length?mr(t,he(n,3),true,true):[]},Ot.dropWhile=function(t,n){return t&&t.length?mr(t,he(n,3),true):[]},Ot.fill=function(t,n,r,e){ +var u=t?t.length:0;if(!u)return[];for(r&&typeof r!="number"&&we(t,n,r)&&(r=0,e=u),u=t.length,r=ju(r),0>r&&(r=-r>u?0:u+r),e=e===F||e>u?u:ju(e),0>e&&(e+=u),e=r>e?0:wu(e);r>>0,r?(t=ku(t))&&(typeof n=="string"||null!=n&&!of(n))&&(n=jr(n),!n&&Wt.test(t))?Ir($(t),0,r):t.split(n,r):[]},Ot.spread=function(t,n){if(typeof t!="function")throw new Hu("Expected a function");return n=n===F?0:Ri(ju(n),0),lr(function(e){var u=e[n];return e=Ir(e,0,n),u&&s(e,u),r(t,this,e)})},Ot.tail=function(t){var n=t?t.length:0;return n?vr(t,1,n):[]},Ot.take=function(t,n,r){return t&&t.length?(n=r||n===F?1:ju(n), +vr(t,0,0>n?0:n)):[]},Ot.takeRight=function(t,n,r){var e=t?t.length:0;return e?(n=r||n===F?1:ju(n),n=e-n,vr(t,0>n?0:n,e)):[]},Ot.takeRightWhile=function(t,n){return t&&t.length?mr(t,he(n,3),false,true):[]},Ot.takeWhile=function(t,n){return t&&t.length?mr(t,he(n,3)):[]},Ot.tap=function(t,n){return n(t),t},Ot.throttle=function(t,n,r){var e=true,u=true;if(typeof t!="function")throw new Hu("Expected a function");return pu(r)&&(e="leading"in r?!!r.leading:e,u="trailing"in r?!!r.trailing:u),ru(t,n,{leading:e,maxWait:n, +trailing:u})},Ot.thru=Ke,Ot.toArray=bu,Ot.toPairs=kf,Ot.toPairsIn=Ef,Ot.toPath=function(t){return tf(t)?l(t,Be):yu(t)?[t]:Ur(vo(t))},Ot.toPlainObject=Au,Ot.transform=function(t,n,r){var e=tf(t)||cf(t);if(n=he(n,4),null==r)if(e||pu(t)){var i=t.constructor;r=e?tf(t)?new i:[]:lu(i)?Xi(_i(t)):{}}else r={};return(e?u:On)(t,function(t,e,u){return n(r,t,e,u)}),r},Ot.unary=function(t){return Qe(t,1)},Ot.union=ko,Ot.unionBy=Eo,Ot.unionWith=Oo,Ot.uniq=function(t){return t&&t.length?wr(t):[]},Ot.uniqBy=function(t,n){ +return t&&t.length?wr(t,he(n,2)):[]},Ot.uniqWith=function(t,n){return t&&t.length?wr(t,F,n):[]},Ot.unset=function(t,n){var r;if(null==t)r=true;else{r=t;var e=n,e=me(e,r)?[e]:Sr(e);r=Ie(r,e),e=Be(Fe(e)),r=!(null!=r&&ei.call(r,e))||delete r[e]}return r},Ot.unzip=Ze,Ot.unzipWith=qe,Ot.update=function(t,n,r){return null==t?t:pr(t,n,(typeof r=="function"?r:Cu)(Rn(t,n)),void 0)},Ot.updateWith=function(t,n,r,e){return e=typeof e=="function"?e:F,null!=t&&(t=pr(t,n,(typeof r=="function"?r:Cu)(Rn(t,n)),e)),t; +},Ot.values=zu,Ot.valuesIn=function(t){return null==t?[]:I(t,Iu(t))},Ot.without=So,Ot.words=Lu,Ot.wrap=function(t,n){return n=null==n?Cu:n,Jo(n,t)},Ot.xor=Io,Ot.xorBy=Ro,Ot.xorWith=zo,Ot.zip=Wo,Ot.zipObject=function(t,n){return Er(t||[],n||[],sn)},Ot.zipObjectDeep=function(t,n){return Er(t||[],n||[],pr)},Ot.zipWith=Bo,Ot.entries=kf,Ot.entriesIn=Ef,Ot.extend=hf,Ot.extendWith=pf,Du(Ot,Ot),Ot.add=Vf,Ot.attempt=Uf,Ot.camelCase=Of,Ot.capitalize=Wu,Ot.ceil=Kf,Ot.clamp=function(t,n,r){return r===F&&(r=n, +n=F),r!==F&&(r=mu(r),r=r===r?r:0),n!==F&&(n=mu(n),n=n===n?n:0),dn(mu(t),n,r)},Ot.clone=function(t){return yn(t,false,true)},Ot.cloneDeep=function(t){return yn(t,true,true)},Ot.cloneDeepWith=function(t,n){return yn(t,true,true,n)},Ot.cloneWith=function(t,n){return yn(t,false,true,n)},Ot.conformsTo=function(t,n){return null==n||xn(t,n,Su(n))},Ot.deburr=Bu,Ot.defaultTo=function(t,n){return null==t||t!==t?n:t},Ot.divide=Gf,Ot.endsWith=function(t,n,r){t=ku(t),n=jr(n);var e=t.length,e=r=r===F?e:dn(ju(r),0,e);return r-=n.length, +0<=r&&t.slice(r,e)==n},Ot.eq=iu,Ot.escape=function(t){return(t=ku(t))&&H.test(t)?t.replace(J,rn):t},Ot.escapeRegExp=function(t){return(t=ku(t))&&ot.test(t)?t.replace(it,"\\$&"):t},Ot.every=function(t,n,r){var e=tf(t)?o:mn;return r&&we(t,n,r)&&(n=F),e(t,he(n,3))},Ot.find=Co,Ot.findIndex=Me,Ot.findKey=function(t,n){return v(t,he(n,3),On)},Ot.findLast=Mo,Ot.findLastIndex=De,Ot.findLastKey=function(t,n){return v(t,he(n,3),Sn)},Ot.floor=Jf,Ot.forEach=Je,Ot.forEachRight=Ye,Ot.forIn=function(t,n){return null==t?t:ro(t,he(n,3),Iu); +},Ot.forInRight=function(t,n){return null==t?t:eo(t,he(n,3),Iu)},Ot.forOwn=function(t,n){return t&&On(t,he(n,3))},Ot.forOwnRight=function(t,n){return t&&Sn(t,he(n,3))},Ot.get=Eu,Ot.gt=Qo,Ot.gte=Xo,Ot.has=function(t,n){return null!=t&&ge(t,n,Bn)},Ot.hasIn=Ou,Ot.head=$e,Ot.identity=Cu,Ot.includes=function(t,n,r,e){return t=fu(t)?t:zu(t),r=r&&!e?ju(r):0,e=t.length,0>r&&(r=Ri(e+r,0)),du(t)?r<=e&&-1r&&(r=Ri(e+r,0)),d(t,n,r)):-1},Ot.inRange=function(t,n,r){return n=xu(n),r===F?(r=n,n=0):r=xu(r),t=mu(t),t>=zi(n,r)&&t=t},Ot.isSet=ff,Ot.isString=du,Ot.isSymbol=yu, +Ot.isTypedArray=cf,Ot.isUndefined=function(t){return t===F},Ot.isWeakMap=function(t){return _u(t)&&"[object WeakMap]"==Et(t)},Ot.isWeakSet=function(t){return _u(t)&&"[object WeakSet]"==oi.call(t)},Ot.join=function(t,n){return t?Si.call(t,n):""},Ot.kebabCase=Sf,Ot.last=Fe,Ot.lastIndexOf=function(t,n,r){var e=t?t.length:0;if(!e)return-1;var u=e;if(r!==F&&(u=ju(r),u=0>u?Ri(e+u,0):zi(u,e-1)),n===n){for(r=u+1;r--&&t[r]!==n;);t=r}else t=g(t,b,u,true);return t},Ot.lowerCase=If,Ot.lowerFirst=Rf,Ot.lt=af,Ot.lte=lf, +Ot.max=function(t){return t&&t.length?An(t,Cu,Wn):F},Ot.maxBy=function(t,n){return t&&t.length?An(t,he(n,2),Wn):F},Ot.mean=function(t){return x(t,Cu)},Ot.meanBy=function(t,n){return x(t,he(n,2))},Ot.min=function(t){return t&&t.length?An(t,Cu,Yn):F},Ot.minBy=function(t,n){return t&&t.length?An(t,he(n,2),Yn):F},Ot.stubArray=Fu,Ot.stubFalse=Nu,Ot.stubObject=function(){return{}},Ot.stubString=function(){return""},Ot.stubTrue=function(){return true},Ot.multiply=Yf,Ot.nth=function(t,n){return t&&t.length?nr(t,ju(n)):F; +},Ot.noConflict=function(){return Pt._===this&&(Pt._=fi),this},Ot.noop=Tu,Ot.now=Po,Ot.pad=function(t,n,r){t=ku(t);var e=(n=ju(n))?T(t):0;return!n||e>=n?t:(n=(n-e)/2,Xr(Ai(n),r)+t+Xr(mi(n),r))},Ot.padEnd=function(t,n,r){t=ku(t);var e=(n=ju(n))?T(t):0;return n&&en){var e=t;t=n,n=e}return r||t%1||n%1?(r=Li(),zi(t+r*(n-t+Tt("1e-"+((r+"").length-1))),n)):cr(t,n)},Ot.reduce=function(t,n,r){var e=tf(t)?h:m,u=3>arguments.length;return e(t,he(n,4),r,u,to)},Ot.reduceRight=function(t,n,r){var e=tf(t)?p:m,u=3>arguments.length;return e(t,he(n,4),r,u,no)},Ot.repeat=function(t,n,r){return n=(r?we(t,n,r):n===F)?1:ju(n),ar(ku(t),n)},Ot.replace=function(){ +var t=arguments,n=ku(t[0]);return 3>t.length?n:n.replace(t[1],t[2])},Ot.result=function(t,n,r){n=me(n,t)?[n]:Sr(n);var e=-1,u=n.length;for(u||(t=F,u=1);++et||9007199254740991=i)return t;if(i=r-T(e),1>i)return e;if(r=o?Ir(o,0,i).join(""):t.slice(0,i),u===F)return r+e;if(o&&(i+=r.length-i),of(u)){if(t.slice(i).search(u)){var f=r;for(u.global||(u=Ju(u.source,ku(gt.exec(u))+"g")),u.lastIndex=0;o=u.exec(f);)var c=o.index;r=r.slice(0,c===F?i:c)}}else t.indexOf(jr(u),i)!=i&&(u=r.lastIndexOf(u),-1u.__dir__?"Right":"")}),u},Dt.prototype[t+"Right"]=function(n){return this.reverse()[t](n).reverse()}}),u(["filter","map","takeWhile"],function(t,n){var r=n+1,e=1==r||3==r;Dt.prototype[t]=function(t){var n=this.clone();return n.__iteratees__.push({iteratee:he(t,3),type:r}),n.__filtered__=n.__filtered__||e,n}}),u(["head","last"],function(t,n){var r="take"+(n?"Right":"");Dt.prototype[t]=function(){return this[r](1).value()[0]}}),u(["initial","tail"],function(t,n){var r="drop"+(n?"":"Right"); +Dt.prototype[t]=function(){return this.__filtered__?new Dt(this):this[r](1)}}),Dt.prototype.compact=function(){return this.filter(Cu)},Dt.prototype.find=function(t){return this.filter(t).head()},Dt.prototype.findLast=function(t){return this.reverse().find(t)},Dt.prototype.invokeMap=lr(function(t,n){return typeof t=="function"?new Dt(this):this.map(function(r){return Mn(r,t,n)})}),Dt.prototype.reject=function(t){return this.filter(uu(he(t)))},Dt.prototype.slice=function(t,n){t=ju(t);var r=this;return r.__filtered__&&(0n)?new Dt(r):(0>t?r=r.takeRight(-t):t&&(r=r.drop(t)), +n!==F&&(n=ju(n),r=0>n?r.dropRight(-n):r.take(n-t)),r)},Dt.prototype.takeRightWhile=function(t){return this.reverse().takeWhile(t).reverse()},Dt.prototype.toArray=function(){return this.take(4294967295)},On(Dt.prototype,function(t,n){var r=/^(?:filter|find|map|reject)|While$/.test(n),e=/^(?:head|last)$/.test(n),u=Ot[e?"take"+("last"==n?"Right":""):n],i=e||/^find/.test(n);u&&(Ot.prototype[n]=function(){function n(t){return t=u.apply(Ot,s([t],f)),e&&h?t[0]:t}var o=this.__wrapped__,f=e?[1]:arguments,c=o instanceof Dt,a=f[0],l=c||tf(o); +l&&r&&typeof a=="function"&&1!=a.length&&(c=l=false);var h=this.__chain__,p=!!this.__actions__.length,a=i&&!h,c=c&&!p;return!i&&l?(o=c?o:new Dt(this),o=t.apply(o,f),o.__actions__.push({func:Ke,args:[n],thisArg:F}),new Mt(o,h)):a&&c?t.apply(this,f):(o=this.thru(n),a?e?o.value()[0]:o.value():o)})}),u("pop push shift sort splice unshift".split(" "),function(t){var n=Qu[t],r=/^(?:push|sort|unshift)$/.test(t)?"tap":"thru",e=/^(?:pop|shift)$/.test(t);Ot.prototype[t]=function(){var t=arguments;if(e&&!this.__chain__){ +var u=this.value();return n.apply(tf(u)?u:[],t)}return this[r](function(r){return n.apply(tf(r)?r:[],t)})}}),On(Dt.prototype,function(t,n){var r=Ot[n];if(r){var e=r.name+"";(Zi[e]||(Zi[e]=[])).push({name:n,func:r})}}),Zi[Jr(F,2).name]=[{name:"wrapper",func:F}],Dt.prototype.clone=function(){var t=new Dt(this.__wrapped__);return t.__actions__=Ur(this.__actions__),t.__dir__=this.__dir__,t.__filtered__=this.__filtered__,t.__iteratees__=Ur(this.__iteratees__),t.__takeCount__=this.__takeCount__,t.__views__=Ur(this.__views__), +t},Dt.prototype.reverse=function(){if(this.__filtered__){var t=new Dt(this);t.__dir__=-1,t.__filtered__=true}else t=this.clone(),t.__dir__*=-1;return t},Dt.prototype.value=function(){var t,n=this.__wrapped__.value(),r=this.__dir__,e=tf(n),u=0>r,i=e?n.length:0;t=i;for(var o=this.__views__,f=0,c=-1,a=o.length;++ci||i==t&&a==t)return Ar(n,this.__actions__);e=[];t:for(;t--&&c=this.__values__.length;return{done:t,value:t?F:this.__values__[this.__index__++]}},Ot.prototype.plant=function(t){for(var n,r=this;r instanceof Rt;){var e=Ce(r);e.__index__=0,e.__values__=F,n?u.__wrapped__=e:n=e;var u=e,r=r.__wrapped__}return u.__wrapped__=t,n},Ot.prototype.reverse=function(){var t=this.__wrapped__;return t instanceof Dt?(this.__actions__.length&&(t=new Dt(this)),t=t.reverse(),t.__actions__.push({func:Ke,args:[Pe],thisArg:F}),new Mt(t,this.__chain__)):this.thru(Pe); +},Ot.prototype.toJSON=Ot.prototype.valueOf=Ot.prototype.value=function(){return Ar(this.__wrapped__,this.__actions__)},Ot.prototype.first=Ot.prototype.head,vi&&(Ot.prototype[vi]=Ge),Ot}();typeof define=="function"&&typeof define.amd=="object"&&define.amd?(Pt._=un, define(function(){return un})):qt?((qt.exports=un)._=un,Zt._=un):Pt._=un}).call(this); diff --git a/resources/lang/en/server.php b/resources/lang/en/server.php index 0f77c1fe8..d8cae3ae2 100644 --- a/resources/lang/en/server.php +++ b/resources/lang/en/server.php @@ -233,6 +233,8 @@ return [ 'command' => 'Startup Command', 'edit_params' => 'Edit Parameters', 'update' => 'Update Startup Parameters', + 'startup_var' => 'Startup Command Variable', + 'startup_regex' => 'Verification Regex', ], 'sftp' => [ 'header' => 'SFTP Configuration', diff --git a/resources/lang/en/strings.php b/resources/lang/en/strings.php index ee2939f31..b3abb2bc1 100644 --- a/resources/lang/en/strings.php +++ b/resources/lang/en/strings.php @@ -63,4 +63,6 @@ return [ '2fa' => '2FA', 'logout' => 'Logout', 'admin_cp' => 'Admin Control Panel', + 'optional' => 'Optional', + 'read_only' => 'Read Only', ]; diff --git a/resources/themes/pterodactyl/admin/index.blade.php b/resources/themes/pterodactyl/admin/index.blade.php new file mode 100644 index 000000000..0385837af --- /dev/null +++ b/resources/themes/pterodactyl/admin/index.blade.php @@ -0,0 +1,71 @@ +{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} + +{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} +{{-- of this software and associated documentation files (the "Software"), to deal --}} +{{-- in the Software without restriction, including without limitation the rights --}} +{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} +{{-- copies of the Software, and to permit persons to whom the Software is --}} +{{-- furnished to do so, subject to the following conditions: --}} + +{{-- The above copyright notice and this permission notice shall be included in all --}} +{{-- copies or substantial portions of the Software. --}} + +{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} +{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} +{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} +{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} +{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} +{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} +{{-- SOFTWARE. --}} +@extends('layouts.admin') + +@section('title') + Administration +@endsection + +@section('content-header') +

Administrative OverviewA quick glance at your system.

+ +@endsection + +@section('content') +
+
+
+
+

System Information

+
+
+ @if (Version::isLatestPanel()) + You are running Pterodactyl Panel version {{ Version::getCurrentPanel() }}. Your panel is up-to-date! + @else + Your panel is not up-to-date! The latest version is {{ Version::getPanel() }} and you are currently running version {{ Version::getCurrentPanel() }}. + @endif +
+
+
+
+ +@endsection diff --git a/resources/themes/pterodactyl/admin/nodes/index.blade.php b/resources/themes/pterodactyl/admin/nodes/index.blade.php new file mode 100644 index 000000000..ddeffde92 --- /dev/null +++ b/resources/themes/pterodactyl/admin/nodes/index.blade.php @@ -0,0 +1,118 @@ +{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} + +{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} +{{-- of this software and associated documentation files (the "Software"), to deal --}} +{{-- in the Software without restriction, including without limitation the rights --}} +{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} +{{-- copies of the Software, and to permit persons to whom the Software is --}} +{{-- furnished to do so, subject to the following conditions: --}} + +{{-- The above copyright notice and this permission notice shall be included in all --}} +{{-- copies or substantial portions of the Software. --}} + +{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} +{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} +{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} +{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} +{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} +{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} +{{-- SOFTWARE. --}} +@extends('layouts.admin') + +@section('title') + List Nodes +@endsection + +@section('scripts') + @parent + {!! Theme::css('vendor/fontawesome/animation.min.css') !!} +@endsection + +@section('content-header') +

NodesAll nodes available on the system.

+ +@endsection + +@section('content') +
+
+
+
+

Node List

+
+
+
+ +
+ + +
+
+
+
+
+
+ + + + + + + + + + + + + @foreach ($nodes as $node) + + + + + + + + + + + @endforeach + +
NameLocationMemoryDiskServersSSLPublic
{{ $node->name }}{{ $node->location->short }}{{ $node->memory }} MB{{ $node->disk }} MB{{ $node->servers_count }}
+
+ +
+
+
+@endsection + +@section('footer-scripts') + @parent + +@endsection diff --git a/resources/themes/pterodactyl/admin/nodes/view/allocation.blade.php b/resources/themes/pterodactyl/admin/nodes/view/allocation.blade.php new file mode 100644 index 000000000..9c9ef5c78 --- /dev/null +++ b/resources/themes/pterodactyl/admin/nodes/view/allocation.blade.php @@ -0,0 +1,244 @@ +{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} + +{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} +{{-- of this software and associated documentation files (the "Software"), to deal --}} +{{-- in the Software without restriction, including without limitation the rights --}} +{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} +{{-- copies of the Software, and to permit persons to whom the Software is --}} +{{-- furnished to do so, subject to the following conditions: --}} + +{{-- The above copyright notice and this permission notice shall be included in all --}} +{{-- copies or substantial portions of the Software. --}} + +{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} +{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} +{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} +{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} +{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} +{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} +{{-- SOFTWARE. --}} +@extends('layouts.admin') + +@section('title') + {{ $node->name }}: Allocations +@endsection + +@section('content-header') +

{{ $node->name }}Control allocations available for servers on this node.

+ +@endsection + +@section('content') +
+ +
+
+
+
+
+

Existing Allocations

+
+
+ + + + + + + + + @foreach($node->allocations as $allocation) + + + + + + + + @endforeach +
IP Address IP AliasPortAssigned To
{{ $allocation->ip }} + + + {{ $allocation->port }} + @if(! is_null($allocation->server)) + {{ $allocation->server->name }} + @endif + + @if(is_null($allocation->server_id)) + + @else + + @endif +
+
+ +
+
+
+
+
+
+

Assign New Allocations

+
+
+
+ +
+ +

Enter an IP address to assign ports to here.

+
+
+
+ +
+ +

If you would like to assign a default alias to these allocations enter it here.

+
+
+
+ +
+ +

Enter individual ports or port ranges here separated by commas or spaces.

+
+
+
+ +
+
+
+
+ +@endsection + +@section('footer-scripts') + @parent + +@endsection diff --git a/resources/themes/pterodactyl/admin/nodes/view/configuration.blade.php b/resources/themes/pterodactyl/admin/nodes/view/configuration.blade.php new file mode 100644 index 000000000..8193eaeb9 --- /dev/null +++ b/resources/themes/pterodactyl/admin/nodes/view/configuration.blade.php @@ -0,0 +1,101 @@ +{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} + +{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} +{{-- of this software and associated documentation files (the "Software"), to deal --}} +{{-- in the Software without restriction, including without limitation the rights --}} +{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} +{{-- copies of the Software, and to permit persons to whom the Software is --}} +{{-- furnished to do so, subject to the following conditions: --}} + +{{-- The above copyright notice and this permission notice shall be included in all --}} +{{-- copies or substantial portions of the Software. --}} + +{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} +{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} +{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} +{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} +{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} +{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} +{{-- SOFTWARE. --}} +@extends('layouts.admin') + +@section('title') + {{ $node->name }}: Configuration +@endsection + +@section('content-header') +

{{ $node->name }}Your daemon configuration file.

+ +@endsection + +@section('content') +
+ +
+
+
+
+
+

Configuration File

+
+
+
{{ $node->getConfigurationAsJson(true) }}
+
+ +
+
+
+
+
+

Auto-Deploy

+
+
+

To simplify the configuration of nodes it is possible to fetch the config from the panel. A token is required for this process. The button below will generate a token and provide you with the commands necessary for automatic configuration of the node. Tokens are only valid for 5 minutes.

+
+ +
+
+
+@endsection + +@section('footer-scripts') + @parent + +@endsection diff --git a/resources/themes/pterodactyl/admin/nodes/view/index.blade.php b/resources/themes/pterodactyl/admin/nodes/view/index.blade.php new file mode 100644 index 000000000..de875c678 --- /dev/null +++ b/resources/themes/pterodactyl/admin/nodes/view/index.blade.php @@ -0,0 +1,163 @@ +{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} + +{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} +{{-- of this software and associated documentation files (the "Software"), to deal --}} +{{-- in the Software without restriction, including without limitation the rights --}} +{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} +{{-- copies of the Software, and to permit persons to whom the Software is --}} +{{-- furnished to do so, subject to the following conditions: --}} + +{{-- The above copyright notice and this permission notice shall be included in all --}} +{{-- copies or substantial portions of the Software. --}} + +{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} +{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} +{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} +{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} +{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} +{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} +{{-- SOFTWARE. --}} +@extends('layouts.admin') + +@section('title') + {{ $node->name }} +@endsection + +@section('content-header') +

{{ $node->name }}A quick overview of your node.

+ +@endsection + +@section('content') +
+ +
+
+
+
+
+
+
+

Information

+
+
+ + + + + + + + + + + + + +
Daemon Version (Latest: {{ Version::getDaemon() }})
System Information
Total CPU Cores
+
+
+
+
+
+
+

Delete Node

+
+
+

Deleting a node is a irreversable action and will immediately remove this node from the panel. There must be no servers associated with this node in order to continue.

+
+ +
+
+
+
+
+
+
+

At-a-Glance

+
+
+
+
+
+ +
+ Disk Space Allocated + {{ $stats['disk']['value'] }} Mb +
+
+
+
+
+
+
+
+ +
+ Memory Allocated + {{ $stats['memory']['value'] }} Mb +
+
+
+
+
+
+
+
+ +
+ Total Servers + {{ $node->servers_count }} +
+
+
+
+
+
+
+
+@endsection + +@section('footer-scripts') + @parent + +@endsection diff --git a/resources/themes/pterodactyl/admin/nodes/view/servers.blade.php b/resources/themes/pterodactyl/admin/nodes/view/servers.blade.php new file mode 100644 index 000000000..0033dac2e --- /dev/null +++ b/resources/themes/pterodactyl/admin/nodes/view/servers.blade.php @@ -0,0 +1,90 @@ +{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} + +{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} +{{-- of this software and associated documentation files (the "Software"), to deal --}} +{{-- in the Software without restriction, including without limitation the rights --}} +{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} +{{-- copies of the Software, and to permit persons to whom the Software is --}} +{{-- furnished to do so, subject to the following conditions: --}} + +{{-- The above copyright notice and this permission notice shall be included in all --}} +{{-- copies or substantial portions of the Software. --}} + +{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} +{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} +{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} +{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} +{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} +{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} +{{-- SOFTWARE. --}} +@extends('layouts.admin') + +@section('title') + {{ $node->name }}: Servers +@endsection + +@section('content-header') +

{{ $node->name }}All servers currently assigned to this node.

+ +@endsection + +@section('content') +
+ +
+
+
+
+
+

Process Manager

+
+
+ + + + + + + + + + + + @foreach($node->servers as $server) + + + + + + + + + + + @endforeach +
IDServer NameOwnerServiceMemoryDiskCPUStatus
{{ $server->uuidShort }}{{ $server->name }}{{ $server->user->username }}{{ $server->service->name }} ({{ $server->option->name }})NaN / {{ $server->memory === 0 ? '∞' : $server->memory }} MB{{ $server->disk }} MBNaN %NaN
+
+
+
+
+@endsection + +@section('footer-scripts') + @parent + {!! Theme::js('js/admin/node/view-servers.js') !!} +@endsection diff --git a/resources/themes/pterodactyl/admin/nodes/view/settings.blade.php b/resources/themes/pterodactyl/admin/nodes/view/settings.blade.php new file mode 100644 index 000000000..74e31a5c4 --- /dev/null +++ b/resources/themes/pterodactyl/admin/nodes/view/settings.blade.php @@ -0,0 +1,223 @@ +{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} + +{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} +{{-- of this software and associated documentation files (the "Software"), to deal --}} +{{-- in the Software without restriction, including without limitation the rights --}} +{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} +{{-- copies of the Software, and to permit persons to whom the Software is --}} +{{-- furnished to do so, subject to the following conditions: --}} + +{{-- The above copyright notice and this permission notice shall be included in all --}} +{{-- copies or substantial portions of the Software. --}} + +{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} +{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} +{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} +{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} +{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} +{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} +{{-- SOFTWARE. --}} +@extends('layouts.admin') + +@section('title') + {{ $node->name }}: Settings +@endsection + +@section('content-header') +

{{ $node->name }}Configure your node settings.

+ +@endsection + +@section('content') +
+ +
+
+
+
+
+
+

Settings

+
+
+
+ +
+ +

Character limits: a-zA-Z0-9_.- and [Space] (min 1, max 100 characters).

+
+
+
+ +
+ +
+
+
+ +
+ public) === '1') ? 'checked' : '' }} id="public_1" checked>
+ public) === '0') ? 'checked' : '' }} id="public_0"> +
+
+
+ +
+ +
+

Please enter domain name (e.g node.example.com) to be used for connecting to the daemon. An IP address may only be used if you are not using SSL for this node. + Why? +

+
+
+ +
+
+ scheme) === 'https') ? 'checked' : '' }}/> +
+
+ scheme) === 'http') ? 'checked' : '' }}/> +
+
+

You should always leave SSL enabled for nodes. Disabling SSL could allow a malicious user to intercept traffic between the panel and the daemon potentially exposing sensitive information.

+
+ +
+
+
+
+
+
+

Allocation Limits

+
+
+
+
+
+ +
+ + MB +
+
+
+ +
+ + % +
+
+
+

Enter the total amount of memory available on this node for allocation to servers. You may also provide a percentage that can allow allocation of more than the defined memory.

+
+
+
+
+ +
+ + MB +
+
+
+ +
+ + % +
+
+
+

Enter the total amount of disk space available on this node for server allocation. You may also provide a percentage that will determine the amount of disk space over the set limit to allow.

+
+
+
+
+
+
+
+

General Configuration

+
+
+
+ +
+ + MB +
+

Enter the maximum size of files that can be uploaded through the web-based file manager.

+
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+

The daemon runs its own SFTP management container and does not use the SSHd process on the main physical server. Do not use the same port that you have assigned for your physcial server's SSH process.

+
+
+
+
+
+
+
+
+
+

Save Settings

+
+
+
+
+ +
+

Resetting the daemon master key will void any request coming from the old key. This key is used for all sensitive operations on the daemon including server creation and deletion. We suggest changing this key regularly for security.

+
+
+ +
+
+
+
+@endsection + +@section('footer-scripts') + @parent + +@endsection diff --git a/resources/themes/pterodactyl/admin/servers/index.blade.php b/resources/themes/pterodactyl/admin/servers/index.blade.php new file mode 100644 index 000000000..820ca47df --- /dev/null +++ b/resources/themes/pterodactyl/admin/servers/index.blade.php @@ -0,0 +1,94 @@ +{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} + +{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} +{{-- of this software and associated documentation files (the "Software"), to deal --}} +{{-- in the Software without restriction, including without limitation the rights --}} +{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} +{{-- copies of the Software, and to permit persons to whom the Software is --}} +{{-- furnished to do so, subject to the following conditions: --}} + +{{-- The above copyright notice and this permission notice shall be included in all --}} +{{-- copies or substantial portions of the Software. --}} + +{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} +{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} +{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} +{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} +{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} +{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} +{{-- SOFTWARE. --}} +@extends('layouts.admin') + +@section('title') + List Servers +@endsection + +@section('content-header') +

ServersAll servers available on the system.

+ +@endsection + +@section('content') +
+
+
+
+

Server List

+
+
+
+ +
+ + +
+
+
+
+
+
+ + + + + + + + + + + @foreach ($servers as $server) + + + + + + + + + @endforeach + +
IDServer NameOwnerNodeConnection
{{ $server->uuidShort }}{{ $server->name }}{{ $server->user->username }}{{ $server->node->name }} + {{ $server->allocation->alias }}:{{ $server->allocation->port }} + + @if($server->suspended && ! $server->trashed()) + Suspended + @elseif($server->trashed()) + Pending Deletion + @elseif(! $server->installed) + Installing + @else + Active + @endif +
+
+ +
+
+
+@endsection diff --git a/resources/themes/pterodactyl/admin/servers/new.blade.php b/resources/themes/pterodactyl/admin/servers/new.blade.php new file mode 100644 index 000000000..fbb0868d6 --- /dev/null +++ b/resources/themes/pterodactyl/admin/servers/new.blade.php @@ -0,0 +1,257 @@ +{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} + +{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} +{{-- of this software and associated documentation files (the "Software"), to deal --}} +{{-- in the Software without restriction, including without limitation the rights --}} +{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} +{{-- copies of the Software, and to permit persons to whom the Software is --}} +{{-- furnished to do so, subject to the following conditions: --}} + +{{-- The above copyright notice and this permission notice shall be included in all --}} +{{-- copies or substantial portions of the Software. --}} + +{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} +{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} +{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} +{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} +{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} +{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} +{{-- SOFTWARE. --}} +@extends('layouts.admin') + +@section('title') + New Server +@endsection + +@section('content-header') +

Create ServerAdd a new server to the panel.

+ +@endsection + +@section('content') +
+
+
+
+
+

Core Details

+
+
+
+ + +

Character limits: a-z A-Z 0-9 _ - . and [Space] (max 200 characters).

+
+
+ + +
+
+
+
+
+
+
+
+ +
+

Allocation Management

+
+
+
+ + +

The location in which this server will be deployed.

+
+
+ + +

The node which this server will be deployed to.

+
+
+ + +

The main allocation that will be assigned to this server.

+
+
+ + +

Additional allocations to assign to this server on creation.

+
+
+ +
+
+
+
+
+
+
+

Resource Management

+
+
+
+ +
+ + MB +
+
+
+ +
+ + MB +
+
+
+ +
+ + + + +
+
+
+ +
+
+ +
+ + MB +
+
+
+ +
+ + % +
+
+
+ +
+ + I/O +
+
+
+ +
+
+
+
+
+
+
+

Service Configuration

+
+
+
+ + +

Select the type of service that this server will be running.

+
+
+ + +

Select the type of sub-service that this server will be running.

+
+
+ + +

Select a service pack to be automatically installed on this server when first created.

+
+
+
+
+
+
+
+

Docker Configuration

+
+
+
+ + +

This is the default Docker container that will be used to run this server.

+
+
+ + +

If you would like to use a custom Docker container please enter it here, otherwise leave empty.

+
+
+
+
+
+
+
+
+
+

Startup Configuration

+
+
+
+ +
+ + +
+

The following data replacers are avaliable for the startup command: @{{SERVER_MEMORY}}, @{{SERVER_IP}}, and @{{SERVER_PORT}}. They will be replaced with the allocated memory, server ip, and server port respectively.

+
+
+
+

Service Variables

+
+
+ +
+
+
+
+@endsection + +@section('footer-scripts') + @parent + {!! Theme::js('vendor/lodash/lodash.js') !!} + {!! Theme::js('js/admin/new-server.js') !!} +@endsection diff --git a/resources/themes/pterodactyl/admin/servers/view/build.blade.php b/resources/themes/pterodactyl/admin/servers/view/build.blade.php new file mode 100644 index 000000000..c01149d97 --- /dev/null +++ b/resources/themes/pterodactyl/admin/servers/view/build.blade.php @@ -0,0 +1,158 @@ +{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} + +{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} +{{-- of this software and associated documentation files (the "Software"), to deal --}} +{{-- in the Software without restriction, including without limitation the rights --}} +{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} +{{-- copies of the Software, and to permit persons to whom the Software is --}} +{{-- furnished to do so, subject to the following conditions: --}} + +{{-- The above copyright notice and this permission notice shall be included in all --}} +{{-- copies or substantial portions of the Software. --}} + +{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} +{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} +{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} +{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} +{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} +{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} +{{-- SOFTWARE. --}} +@extends('layouts.admin') + +@section('title') + Server — {{ $server->name }}: Build Details +@endsection + +@section('content-header') +

{{ $server->name }}Control allocations and system resources for this server.

+ +@endsection + +@section('content') +
+
+ +
+
+
+
+
+
+
+

System Resources

+
+
+
+ +
+ + MB +
+

The maximum amount of memory allowed for this container. Setting this to 0 will allow unlimited memorry in a container.

+
+
+ +
+ + MB +
+

Setting this to 0 will disable swap space on this server. Setting to -1 will allow unlimited swap.

+
+
+ +
+ + % +
+

Each physical core on the system is considered to be 100%. Setting this value to 0 will allow a server to use CPU time without restrictions.

+
+
+ +
+ +
+

Changing this value can have negative effects on all containers on the system. We strongly recommend leaving this value as 500.

+
+ +
+
+
+
+
+
+

Allocation Management

+
+
+
+ + +

The default connection address that will be used for this game server.

+
+
+ +
+ +
+

Please note that due to software limitations you cannot assign identical ports on different IPs to the same server.

+
+
+ +
+ +
+

Simply select which ports you would like to remove from the list above. If you want to assign a port on a different IP that is already in use you can select it from the left and delete it here.

+
+
+ +
+
+
+
+@endsection + +@section('footer-scripts') + @parent + +@endsection diff --git a/resources/themes/pterodactyl/admin/servers/view/database.blade.php b/resources/themes/pterodactyl/admin/servers/view/database.blade.php new file mode 100644 index 000000000..c9ebf506e --- /dev/null +++ b/resources/themes/pterodactyl/admin/servers/view/database.blade.php @@ -0,0 +1,192 @@ +{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} + +{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} +{{-- of this software and associated documentation files (the "Software"), to deal --}} +{{-- in the Software without restriction, including without limitation the rights --}} +{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} +{{-- copies of the Software, and to permit persons to whom the Software is --}} +{{-- furnished to do so, subject to the following conditions: --}} + +{{-- The above copyright notice and this permission notice shall be included in all --}} +{{-- copies or substantial portions of the Software. --}} + +{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} +{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} +{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} +{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} +{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} +{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} +{{-- SOFTWARE. --}} +@extends('layouts.admin') + +@section('title') + Server — {{ $server->name }}: Databases +@endsection + +@section('content-header') +

{{ $server->name }}Manage server databases.

+ +@endsection + +@section('content') +
+
+ +
+
+
+
+
+
+

Active Databases

+
+
+ + + + + + + + + @foreach($server->databases as $database) + + + + + + + + @endforeach +
DatabaseUsernameConnections FromHost
{{ $database->database }}{{ $database->username }}{{ $database->remote }}{{ $database->host->host }}:{{ $database->host->port }} + + +
+
+
+
+
+
+
+

Create New Database

+
+
+
+
+ + +

Select the host database server that this database should be created on.

+
+
+ +
+ s{{ $server->id }}_ + +
+
+
+ + +

This should reflect the IP address that connections are allowed from. Uses standard MySQL notation. If unsure leave as %.

+
+
+ +
+
+
+
+@endsection + +@section('footer-scripts') + @parent + +@endsection diff --git a/resources/themes/pterodactyl/admin/servers/view/delete.blade.php b/resources/themes/pterodactyl/admin/servers/view/delete.blade.php new file mode 100644 index 000000000..5a0cfe297 --- /dev/null +++ b/resources/themes/pterodactyl/admin/servers/view/delete.blade.php @@ -0,0 +1,141 @@ +{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} + +{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} +{{-- of this software and associated documentation files (the "Software"), to deal --}} +{{-- in the Software without restriction, including without limitation the rights --}} +{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} +{{-- copies of the Software, and to permit persons to whom the Software is --}} +{{-- furnished to do so, subject to the following conditions: --}} + +{{-- The above copyright notice and this permission notice shall be included in all --}} +{{-- copies or substantial portions of the Software. --}} + +{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} +{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} +{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} +{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} +{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} +{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} +{{-- SOFTWARE. --}} +@extends('layouts.admin') + +@section('title') + Server — {{ $server->name }}: Delete +@endsection + +@section('content-header') +

{{ $server->name }}Delete this server from the panel.

+ +@endsection + +@section('content') +
+
+ +
+
+
+ @if($server->trashed()) +
+
+
+

Marked for Deletion

+
+
+

This server is currently marked for deletion by the system {{ Carbon::parse($server->deleted_at)->addMinutes(env('APP_DELETE_MINUTES', 10))->diffForHumans() }}.

+

Deleting a server is an irreversible action. All server data (including files and users) will be removed from the system.

+
+ +
+
+ @endif +
+
+
+

Safely Delete Server

+
+
+

This action will attempt to delete the server from both the panel and daemon. If either one reports an error the action will be cancelled.

+

Deleting a server is an irreversible action. All server data (including files and users) will be removed from the system.

+
+ +
+
+
+
+
+

Force Delete Server

+
+
+

This action will attempt to delete the server from both the panel and daemon. The the daemon does not respond, or reports an error the deletion will continue.

+

Deleting a server is an irreversible action. All server data (including files and users) will be removed from the system. This method may leave dangling files on your daemon if it reports an error.

+
+ +
+
+
+@endsection + +@section('footer-scripts') + @parent + +@endsection diff --git a/resources/themes/pterodactyl/admin/servers/view/details.blade.php b/resources/themes/pterodactyl/admin/servers/view/details.blade.php new file mode 100644 index 000000000..618533ae3 --- /dev/null +++ b/resources/themes/pterodactyl/admin/servers/view/details.blade.php @@ -0,0 +1,170 @@ +{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} + +{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} +{{-- of this software and associated documentation files (the "Software"), to deal --}} +{{-- in the Software without restriction, including without limitation the rights --}} +{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} +{{-- copies of the Software, and to permit persons to whom the Software is --}} +{{-- furnished to do so, subject to the following conditions: --}} + +{{-- The above copyright notice and this permission notice shall be included in all --}} +{{-- copies or substantial portions of the Software. --}} + +{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} +{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} +{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} +{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} +{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} +{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} +{{-- SOFTWARE. --}} +@extends('layouts.admin') + +@section('title') + Server — {{ $server->name }}: Details +@endsection + +@section('content-header') +

{{ $server->name }}Edit details for this server including owner and container.

+ +@endsection + +@section('content') +
+
+ +
+
+
+
+
+
+

Base Information

+
+
+
+
+ + +

Character limits: a-zA-Z0-9_- and [Space] (max 35 characters).

+
+
+ + +

You can change the owner of this server by changing this field to an email matching another use on this system. If you do this a new daemon security token will be generated automatically.

+
+
+ + +

This token should not be shared with anyone as it has full control over this server.

+
+
+ +

Resetting this token will cause any requests using the old token to fail.

+
+
+ +
+
+
+
+
+
+

Container Setup

+
+
+
+
+ + +

The docker image to use for this server. The default image for this service and option combination is {{ $server->option->docker_image }}.

+
+
+ +
+
+
+
+@endsection + +@section('footer-scripts') + @parent + +@endsection diff --git a/resources/themes/pterodactyl/admin/servers/view/index.blade.php b/resources/themes/pterodactyl/admin/servers/view/index.blade.php new file mode 100644 index 000000000..f582e4572 --- /dev/null +++ b/resources/themes/pterodactyl/admin/servers/view/index.blade.php @@ -0,0 +1,203 @@ +{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} + +{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} +{{-- of this software and associated documentation files (the "Software"), to deal --}} +{{-- in the Software without restriction, including without limitation the rights --}} +{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} +{{-- copies of the Software, and to permit persons to whom the Software is --}} +{{-- furnished to do so, subject to the following conditions: --}} + +{{-- The above copyright notice and this permission notice shall be included in all --}} +{{-- copies or substantial portions of the Software. --}} + +{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} +{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} +{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} +{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} +{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} +{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} +{{-- SOFTWARE. --}} +@extends('layouts.admin') + +@section('title') + Server — {{ $server->name }} +@endsection + +@section('content-header') +

{{ $server->name }}{{ $server->uuid }}

+ +@endsection + +@section('content') +
+
+ +
+
+
+
+
+
+
+
+

Information

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
UUID{{ $server->uuid }}
Docker Container ID
Docker User ID
Service{{ $server->option->service->name }} :: {{ $server->option->name }}
Name{{ $server->name }}
Memory{{ $server->memory }}MB / {{ $server->swap }}MB
OOM Killer{!! ($server->oom_disabled === 0) ? 'Enabled' : 'Disabled' !!}
Disk Space{{ $server->disk }}MB
Block IO Weight{{ $server->io }}
CPU Limit{{ $server->cpu }}%
Default Connection{{ $server->allocation->ip }}:{{ $server->allocation->port }}
Connection Alias + @if($server->allocation->alias !== $server->allocation->ip) + {{ $server->allocation->alias }}:{{ $server->allocation->port }} + @else + No Alias Assigned + @endif +
+
+
+
+
+
+
+
+
+
+ @if($server->suspended) +
+
+
+

Suspended

+
+
+
+ @endif + @if($server->installed !== 1) +
+
+
+

{{ (! $server->installed) ? 'Installing' : 'Install Failed' }}

+
+
+
+ @endif +
+
+
+

{{ str_limit($server->user->username, 8) }}

+

Server Owner

+
+
+ + More info + +
+
+
+
+
+

{{ str_limit($server->node->name, 8) }}

+

Server Node

+
+
+ + More info + +
+
+
+
+
+
+
+@endsection + +@section('footer-scripts') + @parent + +@endsection diff --git a/resources/themes/pterodactyl/admin/servers/view/manage.blade.php b/resources/themes/pterodactyl/admin/servers/view/manage.blade.php new file mode 100644 index 000000000..4aee0000f --- /dev/null +++ b/resources/themes/pterodactyl/admin/servers/view/manage.blade.php @@ -0,0 +1,127 @@ +{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} + +{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} +{{-- of this software and associated documentation files (the "Software"), to deal --}} +{{-- in the Software without restriction, including without limitation the rights --}} +{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} +{{-- copies of the Software, and to permit persons to whom the Software is --}} +{{-- furnished to do so, subject to the following conditions: --}} + +{{-- The above copyright notice and this permission notice shall be included in all --}} +{{-- copies or substantial portions of the Software. --}} + +{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} +{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} +{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} +{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} +{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} +{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} +{{-- SOFTWARE. --}} +@extends('layouts.admin') + +@section('title') + Server — {{ $server->name }}: Manage +@endsection + +@section('content-header') +

{{ $server->name }}Additional actions to control this server.

+ +@endsection + +@section('content') +
+
+ +
+
+
+
+
+
+

Install Status

+
+
+

If you need to change the install status from uninstalled to installed, or vice versa, you may do so with the button below.

+
+ +
+
+
+
+
+

Rebuild Container

+
+
+

This will trigger a rebuild of the server container when it next starts up. This is useful if you modified the server configuration file manually, or something just didn't work out correctly.

+
+ +
+
+ @if(! $server->suspended) +
+
+
+

Suspend Server

+
+
+

This will suspend the server, stop any running processes, and immediately block the user from being able to access their files or otherwise manage the server through the panel or API.

+
+ +
+
+ @else +
+
+
+

Unsuspend Server

+
+
+

This will unsuspend the server and restore normal user access.

+
+ +
+
+ @endif +
+@endsection diff --git a/resources/themes/pterodactyl/admin/servers/view/startup.blade.php b/resources/themes/pterodactyl/admin/servers/view/startup.blade.php new file mode 100644 index 000000000..5e4175ad2 --- /dev/null +++ b/resources/themes/pterodactyl/admin/servers/view/startup.blade.php @@ -0,0 +1,115 @@ +{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} + +{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} +{{-- of this software and associated documentation files (the "Software"), to deal --}} +{{-- in the Software without restriction, including without limitation the rights --}} +{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} +{{-- copies of the Software, and to permit persons to whom the Software is --}} +{{-- furnished to do so, subject to the following conditions: --}} + +{{-- The above copyright notice and this permission notice shall be included in all --}} +{{-- copies or substantial portions of the Software. --}} + +{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} +{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} +{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} +{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} +{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} +{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} +{{-- SOFTWARE. --}} +@extends('layouts.admin') + +@section('title') + Server — {{ $server->name }}: Startup +@endsection + +@section('content-header') +

{{ $server->name }}Control startup command as well as variables.

+ +@endsection + +@section('content') +
+
+ +
+
+
+
+
+
+
+

Startup Command Modification

+
+
+ +
+ {{ $server->option->display_executable }} + +
+

Edit your server's startup command here. The following variables are available by default: @{{SERVER_MEMORY}}, @{{SERVER_IP}}, and @{{SERVER_PORT}}.

+
+ +
+
+ @foreach($server->option->variables as $variable) +
+
+
+

{{ $variable->name }}

+
+
+ +

{{ $variable->description }}

+

+ @if($variable->required)Required@elseOptional@endif + @if($variable->user_viewable)Visible@elseHidden@endif + @if($variable->user_editable)Editable@elseLocked@endif +

+
+ +
+
+ @endforeach +
+
+@endsection + +@section('footer-scripts') + @parent + +@endsection diff --git a/resources/themes/pterodactyl/admin/settings.blade.php b/resources/themes/pterodactyl/admin/settings.blade.php new file mode 100644 index 000000000..5bc8a2ad3 --- /dev/null +++ b/resources/themes/pterodactyl/admin/settings.blade.php @@ -0,0 +1,100 @@ +{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} + +{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} +{{-- of this software and associated documentation files (the "Software"), to deal --}} +{{-- in the Software without restriction, including without limitation the rights --}} +{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} +{{-- copies of the Software, and to permit persons to whom the Software is --}} +{{-- furnished to do so, subject to the following conditions: --}} + +{{-- The above copyright notice and this permission notice shall be included in all --}} +{{-- copies or substantial portions of the Software. --}} + +{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} +{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} +{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} +{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} +{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} +{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} +{{-- SOFTWARE. --}} +@extends('layouts.admin') + +@section('title') + Settings +@endsection + +@section('content-header') +

Panel SettingsConfigure Pterodactyl to your liking.

+ +@endsection + +@section('content') +
+
+
+
+

Panel Settings

+
+
+
+
+
+ +
+ +

This is the name that is used throughout the panel and in emails sent to clients.

+
+
+
+ +
+ +

This is the default language that all clients will use unless they manually change it.

+
+
+
+
+
+
In order to modify your SMTP settings for sending mail you will need to edit the .env file in this project's root folder.
+
+
+
+
+ +
+ +

The email address that panel emails will be sent from. Note that some SMTP services require this to match for a given API key.

+
+
+
+ +
+ +

The name that emails will appear to come from.

+
+
+
+
+ +
+
+
+
+@endsection diff --git a/resources/themes/pterodactyl/admin/users/index.blade.php b/resources/themes/pterodactyl/admin/users/index.blade.php new file mode 100644 index 000000000..3832c0374 --- /dev/null +++ b/resources/themes/pterodactyl/admin/users/index.blade.php @@ -0,0 +1,84 @@ +{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} + +{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} +{{-- of this software and associated documentation files (the "Software"), to deal --}} +{{-- in the Software without restriction, including without limitation the rights --}} +{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} +{{-- copies of the Software, and to permit persons to whom the Software is --}} +{{-- furnished to do so, subject to the following conditions: --}} + +{{-- The above copyright notice and this permission notice shall be included in all --}} +{{-- copies or substantial portions of the Software. --}} + +{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} +{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} +{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} +{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} +{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} +{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} +{{-- SOFTWARE. --}} +@extends('layouts.admin') + +@section('title') + List Users +@endsection + +@section('content-header') +

UsersAll registered users on the system.

+ +@endsection + +@section('content') +
+
+
+
+

User List

+
+
+
+ +
+ + +
+
+
+
+
+
+ + + + + + + + + + + @foreach ($users as $user) + + + + + + + + + @endforeach + +
ID + Email + Client NameUsernameServers
{{ $user->id }}{{ $user->email }}{{ $user->name_last }}, {{ $user->name_first }}{{ $user->username }}{{ $user->servers_count }}
+
+ +
+
+
+@endsection diff --git a/resources/themes/pterodactyl/admin/users/new.blade.php b/resources/themes/pterodactyl/admin/users/new.blade.php new file mode 100644 index 000000000..ada012874 --- /dev/null +++ b/resources/themes/pterodactyl/admin/users/new.blade.php @@ -0,0 +1,139 @@ +{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} + +{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} +{{-- of this software and associated documentation files (the "Software"), to deal --}} +{{-- in the Software without restriction, including without limitation the rights --}} +{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} +{{-- copies of the Software, and to permit persons to whom the Software is --}} +{{-- furnished to do so, subject to the following conditions: --}} + +{{-- The above copyright notice and this permission notice shall be included in all --}} +{{-- copies or substantial portions of the Software. --}} + +{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} +{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} +{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} +{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} +{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} +{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} +{{-- SOFTWARE. --}} +@extends('layouts.admin') + +@section('title') + Create User +@endsection + +@section('content-header') +

Create UserAdd a new user to the system.

+ +@endsection + +@section('content') +
+
+
+
+
+

Identity

+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
+
+
+
+

Permissions

+
+
+
+ +
+ +

Setting this to 'Yes' gives a user full administrative access.

+
+
+
+
+
+
+
+
+

Password

+
+
+
+

Providing a user password is optional. New user emails prompt users to create a password the first time they login. If a password is provided here you will need to find a different method of providing it to the user.

+
+ +
+ +
+ +
+
+
+ +
+
+
+
+
+
+@endsection + +@section('footer-scripts') + @parent + +@endsection diff --git a/resources/views/admin/users/view.blade.php b/resources/themes/pterodactyl/admin/users/view.blade.php similarity index 50% rename from resources/views/admin/users/view.blade.php rename to resources/themes/pterodactyl/admin/users/view.blade.php index 3ba9ec035..facddb1f5 100644 --- a/resources/views/admin/users/view.blade.php +++ b/resources/themes/pterodactyl/admin/users/view.blade.php @@ -1,5 +1,4 @@ {{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Some Modifications (c) 2015 Dylan Seidt --}} {{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} {{-- of this software and associated documentation files (the "Software"), to deal --}} @@ -21,23 +20,27 @@ @extends('layouts.admin') @section('title') - Viewing User + Manager User: {{ $user->username }} +@endsection + +@section('content-header') +

{{ $user->name_first }} {{ $user->name_last}}{{ $user->username }}

+ @endsection @section('content') -
- -

Viewing User: {{ $user->email }}

-

Registered {{ (new Carbon($user->created_at))->toRfc1123String() }}

-
-
-
-
-
+
+ +
+
+
+

Identity

+
+
@@ -62,14 +65,19 @@
-
- {!! csrf_field() !!} - -
-
+
+
-
-
+
+
+
+
+

Password

+
+
@@ -81,91 +89,97 @@
-
+
+
+
+
+
+

Permissions

+
+

Setting this to 'Yes' gives a user full administrative access.

- -
-
-
-

Associated Servers


- @if($user->servers) - + + +
+
+
+

Associated Servers

+
+
+
- - @foreach($user->servers as $server) - - - - - - - - - @endforeach + @foreach($user->servers as $server) + + + + + + + + @endforeach
Identifier Server Name NodeUsername
{{ $server->uuidShort }}{{ $server->name }}{{ $server->node->name }}{{ $server->username }}@if($server->suspended === 0)Active@elseSuspended@endif
{{ $server->uuidShort }}{{ $server->name }}{{ $server->node->name }}@if($server->suspended === 0)Active@elseSuspended@endif
- @else -
There are no servers associated with this account.
- @endif - -
-
-
-
-

Delete Account


-
Warning! There most be no servers associated with this account in order for it to be deleted.
-
- {!! method_field('DELETE') !!} - {!! csrf_field() !!} - +
+
+
+
+

Delete User

+
+
+

There most be no servers associated with this account in order for it to be deleted.

+
+ +
+
- +@endsection + +@section('footer-scripts') + @parent + @endsection diff --git a/resources/themes/pterodactyl/layouts/admin.blade.php b/resources/themes/pterodactyl/layouts/admin.blade.php new file mode 100644 index 000000000..96b8c0e98 --- /dev/null +++ b/resources/themes/pterodactyl/layouts/admin.blade.php @@ -0,0 +1,204 @@ +{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} + +{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} +{{-- of this software and associated documentation files (the "Software"), to deal --}} +{{-- in the Software without restriction, including without limitation the rights --}} +{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} +{{-- copies of the Software, and to permit persons to whom the Software is --}} +{{-- furnished to do so, subject to the following conditions: --}} + +{{-- The above copyright notice and this permission notice shall be included in all --}} +{{-- copies or substantial portions of the Software. --}} + +{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} +{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} +{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} +{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} +{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} +{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} +{{-- SOFTWARE. --}} + + + + + + {{ Settings::get('company', 'Pterodactyl') }} - @yield('title') + + + @section('scripts') + {!! Theme::css('vendor/select2/select2.min.css') !!} + {!! Theme::css('vendor/bootstrap/bootstrap.min.css') !!} + {!! Theme::css('vendor/adminlte/admin.min.css') !!} + {!! Theme::css('vendor/adminlte/colors/skin-blue.min.css') !!} + {!! Theme::css('vendor/sweetalert/sweetalert.min.css') !!} + {!! Theme::css('vendor/animate/animate.min.css') !!} + {!! Theme::css('css/pterodactyl.css') !!} + + + + + @show + + +
+
+ + +
+ +
+
+ @yield('content-header') +
+
+
+
+ @if (count($errors) > 0) +
+ @lang('base.validation_error')

+
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif + @foreach (Alert::getMessages() as $type => $messages) + @foreach ($messages as $message) + + @endforeach + @endforeach +
+
+ @yield('content') +
+
+ + +
+
+ @section('footer-scripts') + {!! Theme::js('js/laroute.js') !!} + {!! Theme::js('js/vendor/jquery/jquery.min.js') !!} + {!! Theme::js('vendor/sweetalert/sweetalert.min.js') !!} + {!! Theme::js('vendor/bootstrap/bootstrap.min.js') !!} + {!! Theme::js('vendor/slimscroll/jquery.slimscroll.min.js') !!} + {!! Theme::js('vendor/adminlte/app.min.js') !!} + {!! Theme::js('js/vendor/socketio/socket.io.min.js') !!} + {!! Theme::js('vendor/bootstrap-notify/bootstrap-notify.min.js') !!} + {!! Theme::js('vendor/select2/select2.full.min.js') !!} + {!! Theme::js('js/admin/functions.js') !!} + @show + + diff --git a/resources/themes/pterodactyl/layouts/master.blade.php b/resources/themes/pterodactyl/layouts/master.blade.php index b5dede0f2..2364f480c 100644 --- a/resources/themes/pterodactyl/layouts/master.blade.php +++ b/resources/themes/pterodactyl/layouts/master.blade.php @@ -62,14 +62,14 @@ +
  • + +
  • @if(Auth::user()->isRootAdmin())
  • @endif -
  • - -
  • diff --git a/resources/themes/pterodactyl/server/settings/databases.blade.php b/resources/themes/pterodactyl/server/settings/databases.blade.php index b69b0f36d..5e90be84f 100644 --- a/resources/themes/pterodactyl/server/settings/databases.blade.php +++ b/resources/themes/pterodactyl/server/settings/databases.blade.php @@ -49,17 +49,19 @@ @lang('strings.username') @lang('strings.password') @lang('server.config.database.host') + @can('reset-db-password', $server)@endcan @foreach($databases as $database) - {{ $database->database }} - {{ $database->username }} - {{ Crypt::decrypt($database->password) }} - @can('reset-db-password', $server) - - @endcan - - {{ $database->a_host }}:{{ $database->a_port }} + {{ $database->database }} + {{ $database->username }} + {{ Crypt::decrypt($database->password) }} + {{ $database->a_host }}:{{ $database->a_port }} + @can('reset-db-password', $server) + + + + @endcan @endforeach @@ -88,10 +90,10 @@ {!! Theme::js('js/frontend/server.socket.js') !!} @endsection diff --git a/resources/views/admin/index.blade.php b/resources/views/admin/index.blade.php deleted file mode 100644 index aa0915a84..000000000 --- a/resources/views/admin/index.blade.php +++ /dev/null @@ -1,58 +0,0 @@ -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} -@extends('layouts.admin') - -@section('title') - Administration -@endsection - -@section('content') -
    -
    - -

    Pterodactyl Admin Control Panel


    - @if (Version::isLatestPanel()) -
    You are running Pterodactyl Panel version {{ Version::getCurrentPanel() }}. Your panel is up-to-date!
    - @else -
    - Your panel is not up-to-date! The latest version is {{ Version::getPanel() }} and you are currently running version {{ Version::getCurrentPanel() }}. -
    - @endif -
    -
    - - -@endsection diff --git a/resources/views/admin/nodes/index.blade.php b/resources/views/admin/nodes/index.blade.php deleted file mode 100644 index de1687087..000000000 --- a/resources/views/admin/nodes/index.blade.php +++ /dev/null @@ -1,96 +0,0 @@ -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} -@extends('layouts.admin') - -@section('title') - Node List -@endsection - -@section('scripts') - @parent - {!! Theme::css('css/vendor/fontawesome/animation.min.css') !!} -@endsection - -@section('content') -
    - -

    All Nodes


    - - - - - - - - - - - - - - - @foreach ($nodes as $node) - - - - - - - - - - - @endforeach - -
    NameLocationSSL
    {{ $node->name }}{{ $node->location->short }}
    -
    -
    {!! $nodes->render() !!}
    -
    -
    - -@endsection diff --git a/resources/views/admin/nodes/new.blade.php b/resources/views/admin/nodes/new.blade.php deleted file mode 100644 index 37add9812..000000000 --- a/resources/views/admin/nodes/new.blade.php +++ /dev/null @@ -1,187 +0,0 @@ -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} -@extends('layouts.admin') - -@section('title') - Create Node -@endsection - -@section('content') -
    - -

    Create New Node


    -
    -
    -
    -
    - -
    - -

    Character limits: a-zA-Z0-9_.- and [Space] (min 1, max 100 characters).

    -
    -
    -
    - -
    - -
    -
    -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -
    - -
    - -
    -

    Please enter domain name (e.g node.example.com) to be used for connecting to the daemon. An IP address may only be used if you are not using SSL for this node. - Why? -

    -
    -
    - -
    -
    - -
    -
    - -
    -
    -

    You should always leave SSL enabled for nodes. Disabling SSL could allow a malicious user to intercept traffic between the panel and the daemon potentially exposing sensitive information.

    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - - MB -
    -
    -
    - -
    - - % -
    -
    -
    -
    -
    -

    Enter the total amount of memory avaliable for new servers. If you would like to allow overallocation of memory enter the percentage that you want to allow. To disable checking for overallocation enter -1 into the field. Entering 0 will prevent creating new servers if it would put the node over the limit.

    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - - MB -
    -
    -
    - -
    - - % -
    -
    -
    -
    -
    -

    Enter the total amount of disk space avaliable for new servers. If you would like to allow overallocation of disk space enter the percentage that you want to allow. To disable checking for overallocation enter -1 into the field. Entering 0 will prevent creating new servers if it would put the node over the limit.

    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - -
    -

    The location at which your server files will be stored. Most users do not need to change this.

    -
    -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    -

    The daemon runs its own SFTP management container and does not use the SSHd process on the main physical server. Do not use the same port that you have assigned for your physcial server's SSH process.

    -
    -
    -
    -
    -
    -
    - {!! csrf_field() !!} - -
    -
    -
    -
    -
    - -@endsection diff --git a/resources/views/admin/nodes/remote/deploy.blade.php b/resources/views/admin/nodes/remote/deploy.blade.php deleted file mode 100644 index b7283fcfd..000000000 --- a/resources/views/admin/nodes/remote/deploy.blade.php +++ /dev/null @@ -1,292 +0,0 @@ -#!/bin/bash -#### - # Pterodactyl - Panel - # Copyright (c) 2015 - 2017 Dane Everitt - # - # Permission is hereby granted, free of charge, to any person obtaining a copy - # of this software and associated documentation files (the "Software"), to deal - # in the Software without restriction, including without limitation the rights - # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - # copies of the Software, and to permit persons to whom the Software is - # furnished to do so, subject to the following conditions: - # - # The above copyright notice and this permission notice shall be included in all - # copies or substantial portions of the Software. - # - # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - # SOFTWARE. -#### -set +e -export DEBIAN_FRONTEND=noninteractive - -INSTALL_DIR="/srv/daemon" -DATA_DIR="{{ $node->daemonBase }}" -CURRENT_SYSTEM_KERNEL="$(uname -r)" -DL_VERSION="0.0.1" - -command_exists() { - command -v "$@" > /dev/null 2>&1 -} - -error_message() { - echo -e "\e[1m\e[97m\e[41m$1\e[0m" - exit 1 -} - -warning_message() { - echo -e "\e[43m\e[30m$1\e[0m" -} - -success_message() { - echo -e "\e[32m$1\e[0m" -} - -running_command() { - echo -e " ;; \e[47m\e[30m$1\e[0m" -} - -for i in "$@" -do - case $i in - -d|--directory) - INSTALL_DIR="$2" - ;; - -a|--datadir) - DATA_DIR="$2" - ;; - -g|--git) - USE_GIT=true - ;; - -u|--unstable) - USE_UNSTABLE=true - USE_GIT=true - ;; - -v|--version) - DL_VERSION="$2" - ;; - -h|--help) - echo "./installer [opts]" - echo " -d | --directory The directory to install the daemon into. (default: /srv/daemon)" - echo " -a | --datadir The directory that daemon users will be stored in. (default: /srv/daemon-data)" - echo " -g | --git Use this flag to download the daemon using a git clone. (default: false)" - echo " -u | --unstable Install unstable version of the daemon, automatically uses --git flag. (default: false)" - echo " -v | --version The version of the daemon to download." - exit - ;; - esac -shift -done - -warning_message "This program will automatically configure your system to run the Pterodactyl Daemon." -warning_message " - Install Location: $INSTALL_DIR" -warning_message " - Data Location: $DATA_DIR" -warning_message "This script will continue in 10 seconds. Press CRTL+C to exit now." -sleep 10 - -# Super basic system detection -if command_exists apt-get; then - INSTALL_CMD="apt-get -y" -elif command_exists yum; then - INSTALL_CMD="yum -y" -else - error_message "No supported repository manager was found." -fi - -if ! command_exists curl; then - warning_message "No file retrieval method found, installing curl now..." - running_command "$INSTALL_CMD -y install curl" - $INSTALL_CMD -y install curl - if [ "$?" -ne "0" ]; then - error_message "Unable to install curl and no other method was found for retrieving files." - fi -fi - -# Determine if the kernel is high enough version. -if command_exists awk; then - PROCESSED_KERNEL_VERSION=$(awk -F. '{print $1$2}' <<< $CURRENT_SYSTEM_KERNEL) -elif command_exists cut; then - PROCESSED_KERNEL_VERSION=$(cut -d. -f1-2 --output-delimiter='' <<< $CURRENT_SYSTEM_KERNEL) -else - error_message "You seem to be missing some core tools that this script needs: awk (or) cut" -fi - -if [ "$PROCESSED_KERNEL_VERSION" -lt "310" ]; then - error_message "Your kernel version must be at least 3.10 or higher for the daemon to work. You are using $CURRENT_SYSTEM_KERNEL" -fi - -check_cgroups() { - # Check CGroups - CGROUP_DIRECTORY_LISTING="$(awk '/[, ](cpu|cpuacct|cpuset|devices|freezer|memory)[, ]/ && $3 == "cgroup" { print $2 }' /proc/mounts | head -n1)" - if [ ! -z $CGROUP_DIRECTORY_LISTING -a -d $CGROUP_DIRECTORY_LISTING ]; then - CGROUP_DIRECTORY="$(dirname $CGROUP_DIRECTORY_LISTING 2>&1)" - if [ -d "$CGROUP_DIRECTORY/cpu" -a -d "$CGROUP_DIRECTORY/cpuacct" -a -d "$CGROUP_DIRECTORY/cpuset" -a -d "$CGROUP_DIRECTORY/devices" -a -d "$CGROUP_DIRECTORY/freezer" -a -d "$CGROUP_DIRECTORY/memory" ]; then - success_message "cgroups enabled and are valid on this machine." - else - error_message "You appear to be missing some important cgroups on this machine." - fi - else - if [ ! -e "/proc/cgroups" ]; then - error_message "This kernel does not appear to support cgroups! Please see https://gist.github.com/DaneEveritt/0f071f481b4d3fa637d4 for more information." - elif [ ! -d "/sys/fs/cgroup" ]; then - error_message "This kernel does not appear to support cgroups! Please see https://gist.github.com/DaneEveritt/0f071f481b4d3fa637d4 for more information." - fi - - if [ ! -f "/tmp/mount_cgroup.sh" ]; then - # Try to enable cgroups - warning_message "Attempting to enable cgroups on this machine..." - running_command "curl -L https://raw.githubusercontent.com/tianon/cgroupfs-mount/master/cgroupfs-mount > /tmp/mount_cgroup.sh" - curl -L https://raw.githubusercontent.com/tianon/cgroupfs-mount/master/cgroupfs-mount > /tmp/mount_cgroup.sh - - running_command "chmod +x /tmp/mount_cgroup.sh" - chmod +x /tmp/mount_cgroup.sh - - running_command "bash /tmp/mount_cgroup.sh" - bash /tmp/mount_cgroup.sh - check_cgroups - else - rm -rf /tmp/mount_cgroup.sh > /dev/null 2>&1 - error_message "Failed to enable cgroups on this machine." - fi - fi -} - -# Check those groups. -check_cgroups - -# Lets install the dependencies. -$INSTALL_CMD install linux-image-extra-$CURRENT_SYSTEM_KERNEL -if [ "$?" -ne "0" ]; then - warning_message "You appear to have a non-generic kernel meaning we could not install extra kernel tools." - warning_message "We will continue to install, but some docker enhancements might not work as expected." - warning_message "Continuing in 10 seconds, press CTRL+C to cancel this script." - sleep 10 -fi - -success_message "Installing Docker..." -running_command "curl -L https://get.docker.com/ | sh" -curl -L https://get.docker.com/ | sh -if [ "$?" -ne "0" ]; then - error_message "Unable to install docker, an error occured!" -fi; - -success_message "Installing NodeJS 5.x..." -running_command "curl -L https://deb.nodesource.com/setup_5.x | sudo -E bash -" -curl -L https://deb.nodesource.com/setup_5.x | sudo -E bash - -if [ "$?" -ne "0" ]; then - error_message "Unable to configure NodeJS, an error occured!" -fi; - -running_command "$INSTALL_CMD install tar nodejs" -$INSTALL_CMD install tar nodejs -if [ "$?" -ne "0" ]; then - error_message "Unable to install NodeJS or Tar, an error occured!" -fi; - -running_command "mkdir -p $INSTALL_DIR $DATA_DIR" -mkdir -p $INSTALL_DIR $DATA_DIR -cd $INSTALL_DIR - -if [ -z $USE_UNSTABLE -a -z $USE_GIT ]; then - CLEANUP_PROGRAMS="nodejs docker-engine" - - running_command "curl -sI https://github.com/Pterodactyl/Daemon/archive/$DL_VERSION.tar.gz | head -n1 | cut -d$' ' -f2" - GITHUB_STATUS="$(curl -sI https://github.com/Pterodactyl/Daemon/archive/$DL_VERSION.tar.gz | head -n1 | cut -d$' ' -f2)" - if [ $GITHUB_STATUS -ne "200" ]; then - $INSTALL_CMD remove $CLEANUP_PROGRAMS 2>&1 - error_message "Github returned a non-200 response code ($GITHUB_STATUS)" - fi - - running_command "curl -L \"https://github.com/Pterodactyl/Daemon/archive/$DL_VERSION.tar.gz\" > daemon.tar.gz" - curl -L "https://github.com/Pterodactyl/Daemon/archive/$DL_VERSION.tar.gz" > daemon.tar.gz - - running_command "tar --strip-components=1 -xzvf daemon.tar.gz" - tar --strip-components=1 -xzvf daemon.tar.gz 2>&1 - if [ "$?" -ne "0" ]; then - $INSTALL_CMD remove $CLEANUP_PROGRAMS 2>&1 - cd ~ && rm -rf $INSTALL_DIR 2>&1 - error_message "Unable to install the daemon due to an error while attempting to unpack files." - fi -elif [ $USE_GIT ]; then - CLEANUP_PROGRAMS="nodejs docker-engine git" - running_command "$INSTALL_CMD install git" - $INSTALL_CMD install git - - running_command "git clone https://github.com/Pterodactyl/Daemon.git ." - git clone https://github.com/Pterodactyl/Daemon.git . - if [ -z $USE_UNSTABLE ]; then - running_command "git checkout tags/$DL_VERSION" - git checkout tags/$DL_VERSION - fi - if [ "$?" -ne "0" ]; then - $INSTALL_CMD remove $CLEANUP_PROGRAMS 2>&1 - cd ~ && rm -rf $INSTALL_DIR 2>&1 - error_message "Unable to install the daemon due to an error while attempting to clone files to the server." - fi -else - error_message "Could not match an install method!" -fi - -running_command "npm install --production" -npm install --production -if [ "$?" -ne "0" ]; then - $INSTALL_CMD remove $CLEANUP_PROGRAMS 2>&1 - cd ~ && rm -rf $INSTALL_DIR 2>&1 - error_message "Unable to install the daemon due to an error that occured while running npm install." -fi - -running_command "docker run -d --name ptdl-sftp -p 2022:22 -v $DATA_DIR:/sftp-root -v $INSTALL_DIR/config/credentials:/creds quay.io/pterodactyl/scrappy" -docker run -d --name ptdl-sftp -p 2022:22 -v $DATA_DIR:/sftp-root -v $INSTALL_DIR/config/credentials:/creds quay.io/pterodactyl/scrappy -if [ "$?" -ne "0" ]; then - $INSTALL_CMD remove $CLEANUP_PROGRAMS 2>&1 - cd ~ && rm -rf $INSTALL_DIR 2>&1 - error_message "Unable to install the daemon due to an error while creating a SFTP container." -fi - -echo '{ - "web": { - "listen": {{ $node->daemonListen }}, - "ssl": { - "enabled": {{ $node->sceheme === 'https' ? 'true' : 'false' }}, - "certificate": "/etc/letsencrypt/live/{{ $node->fqdn }}/fullchain.pem", - "key": "/etc/letsencrypt/live/{{ $node->fqdn }}/privkey.pem" - } - }, - "docker": { - "socket": "/var/run/docker.sock" - }, - "sftp": { - "path": "{{ $node->daemonBase }}", - "port": {{ $node->daemonSFTP }}, - "container": "ptdl-sftp" - }, - "logger": { - "path": "logs/", - "src": false, - "level": "info", - "period": "1d", - "count": 3 - }, - "remote": { - "download": "{{ route('remote.download') }}", - "installed": "{{ route('remote.install') }}" - }, - "uploads": { - "maximumSize": 100000000 - }, - "keys": [ - "{{ $node->daemonSecret }}" - ] -}' > config/core.json -if [ "$?" -ne "0" ]; then - $INSTALL_CMD remove $CLEANUP_PROGRAMS - cd ~ && rm -rf $INSTALL_DIR 2>&1 - error_message "An error occured while attempting to save the JSON file." -fi - -success_message "Congratulations, the daemon is now installed." -exit diff --git a/resources/views/admin/nodes/view.blade.php b/resources/views/admin/nodes/view.blade.php deleted file mode 100644 index 5a0879b72..000000000 --- a/resources/views/admin/nodes/view.blade.php +++ /dev/null @@ -1,814 +0,0 @@ -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} -@extends('layouts.admin') - -@section('title') - Managing Node: {{ $node->name }} -@endsection - -@section('scripts') - @parent - {!! Theme::js('js/vendor/socketio/socket.io.min.js') !!} - {!! Theme::js('js/bootstrap-notify.min.js') !!} - {!! Theme::js('js/vendor/chartjs/chart.min.js') !!} - {!! Theme::js('js/vendor/jquery/jquery-dateFormat.min.js') !!} - -@endsection - -@section('content') -
    - - -
    -
    -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Daemon Version (Latest: {{ Version::getDaemon() }})
    System Information
    Total CPU Cores
    Total Servers{{ count($node->servers) }}
    Memory Allocated{{ is_numeric($stats->memory) ? $stats->memory : 0 }} MB of - @if(!is_null($node->memory_overallocate)) - {{ $node->memory }} - @else - {{ $node->memory }} - @endif - MB -
    Disk Allocated{{ is_numeric($stats->disk) ? $stats->disk : 0 }} MB of - @if(!is_null($node->disk_overallocate)) - {{ $node->disk }} - @else - {{ $node->disk }} - @endif - MB -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - Changing some details below may require that you change the configuration file on the node as well as restart the daemon. They have been marked with below. -
    -
    -
    - -
    - -

    Character limits: a-zA-Z0-9_.- and [Space] (min 1, max 100 characters).

    -
    -
    -
    - -
    - -
    -
    -
    - -
    - public) === '1') ? 'checked' : '' }} id="public_1" checked>
    - public) === '0') ? 'checked' : '' }} id="public_0"> -
    -
    -
    -
    -
    - -
    - -
    -

    Please enter domain name (e.g node.example.com) to be used for connecting to the daemon. An IP address may only be used if you are not using SSL for this node. - Why? -

    -
    -
    - -
    -
    - scheme) === 'https') ? 'checked' : '' }}/> -
    -
    - scheme) === 'http') ? 'checked' : '' }}/> -
    -
    -

    You should always leave SSL enabled for nodes. Disabling SSL could allow a malicious user to intercept traffic between the panel and the daemon potentially exposing sensitive information.

    -
    -
    -
    -
    -
    -
    -
    - -
    - - MB -
    -
    -
    - -
    - - % -
    -
    -
    - -
    - - MB -
    -
    -
    - -
    - - % -
    -
    -
    -
    -
    -

    Enter the total amount of disk space and memory avaliable for new servers. If you would like to allow overallocation of disk space or memory enter the percentage that you want to allow. To disable checking for overallocation enter -1 into the field. Entering 0 will prevent creating new servers if it would put the node over the limit.

    -
    -
    -
    -
    -
    -
    -
    - -
    - - MB -
    -

    Enter the maximum size of files that can be uploaded through the web-based file manager.

    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    -
    -
    -

    The daemon runs its own SFTP management container and does not use the SSHd process on the main physical server. Do not use the same port that you have assigned for your physcial server's SSH process.

    -
    -
    -
    -
    -
    -
    - -
    - Reset Daemon Master Key -
    -
    -
    -

    Resetting the daemon master key will void any request coming from the old key. This key is used for all sensitive operations on the daemon including server creation and deletion. We suggest changing this key regularly for security.

    -
    -
    -
    -
    -
    -
    -
    -
    -
    - {!! csrf_field() !!} - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    To simplify the configuration of nodes it is possible to fetch the config from the panel. A token is required for this process. The button below will generate a token and provide you with the commands necessary for automatic configuration of the node. Be aware that these tokens are only valid for 5 minutes.

    -
    -
    -

    -
    -
    -
    {{ $node->getConfigurationAsJson(true) }}
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Allocate Additional Ports

    -
    -
    -
    - - -
    -
    - -
    -
      -
    • - -
    • -
    -
    -

    You must enter a comma (,) or press the enter key after each port or range that you enter. They should appear in a blue box.

    - -
    -
    - - -
    -
    -
    -
    -
    - {!! csrf_field() !!} - - -
    -
    -
    -
    -
    -
    -
    - - - - - - - - - - @foreach($node->allocations as $allocation) - - - - - - - - @endforeach - -
    IP Address IP AliasPortAssigned To
    {{ $allocation->ip }} - - - {{ $allocation->port }} - @if(! is_null($allocation->server)) - {{ $allocation->server->name }} - @endif - - @if(is_null($allocation->server_id)) - - @else - - @endif -
    -
    - {{ $node->allocations->appends(['tab' => 'tab_allocation'])->render() }} -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - The data below is live output from the daemon. CPU usage is displayed relative to the assigned CPU allocation. For example, if a server is assigned 10% and the CPU usage below displays 90% that means the server is using 9% of the total system CPU. -
    - - - - - - - - - - - - - - @foreach($node->servers as $server) - - - - - - - - - - @endforeach - -
    NameOwnerServiceMemoryDiskCPUStatus
    {{ $server->name }}{{ $server->user->email }}{{ $server->service->name }}-- / {{ $server->memory === 0 ? '∞' : $server->memory }} MB{{ $server->disk }} MB-- %--
    -
    -
    -
    - @if(count($node->servers) === 0) -
    -
    -
    -
    -
    -
    -
    - {!! method_field('DELETE') !!} - {!! csrf_field() !!} - -
    -
    -
    -
    Deleting this node is a permanent action, it cannot be undone.
    -
    -
    -
    -
    -
    - @endif -
    -
    -
    -
    -
    - - -@endsection diff --git a/resources/views/admin/servers/index.blade.php b/resources/views/admin/servers/index.blade.php deleted file mode 100644 index e2e5010f5..000000000 --- a/resources/views/admin/servers/index.blade.php +++ /dev/null @@ -1,83 +0,0 @@ -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} -@extends('layouts.admin') - -@section('title') - Server List -@endsection - -@section('content') -
    - -

    All Servers


    -
    -
    - -
    - -
    -
    -
    - - - - - - - - - - - @foreach ($servers as $server) - suspended === 1 && !$server->trashed()) - class="warning" - @elseif($server->trashed()) - class="danger" - @endif - data-server="{{ $server->uuidShort }}"> - - - - - - @endforeach - -
    Server NameOwnerNode
    - {{ $server->name }} - @if($server->suspended === 1 && !$server->trashed()) - Suspended - @elseif($server->trashed()) - Pending Deletion - @endif - {{ $server->user->email }}{{ $server->node->name }}
    -
    -
    {!! $servers->render() !!}
    -
    -
    - -@endsection diff --git a/resources/views/admin/servers/new.blade.php b/resources/views/admin/servers/new.blade.php deleted file mode 100644 index dc1deef22..000000000 --- a/resources/views/admin/servers/new.blade.php +++ /dev/null @@ -1,511 +0,0 @@ -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} -@extends('layouts.admin') - -@section('title') - Create New Server -@endsection - -@section('scripts') - @parent - {!! Theme::js('js/vendor/typeahead/typeahead.min.js') !!} -@endsection - -@section('content') -
    - -

    Create New Server


    -
    -
    -
    -
    - -
    - -

    Character limits: a-z A-Z 0-9 _ - . and [Space] (max 200 characters).

    -
    -
    -
    - -
    - {{-- Hacky workaround to prevent Safari and Chrome from trying to suggest emails here --}} - - -
    -
    -
    -
    -
    -
    -
    - -
    - -
    - -

    The location in which this server will be deployed.

    -
    -
    - -
    -
    - - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - - MB -
    -
    -
    - -
    - - MB -
    -
    -
    - -
    - - - - - Disable OOM Killer - -
    -
    -
    -
    -
    -

    If you do not want to assign swap space to a server simply put 0 for the value, or -1 to allow unlimited swap space. If you want to disable memory limiting on a server simply enter 0 into the memory field. We suggest leaving OOM Killer enabled unless you know what you are doing, disabling it could cause your server to hang unexpectedly.

    -

    -
    -
    -
    - -
    - - MB -
    -
    -
    - -
    - - % -
    -
    -
    - -
    - - I/O -
    -
    -
    -
    -
    -

    If you do not want to limit CPU usage set the value to 0. To determine a value, take the number physical cores and multiply it by 100. For example, on a quad core system (4 * 100 = 400) there is 400% available. To limit a server to using half of a single core, you would set the value to 50. To allow a server to use up to two physical cores, set the value to 200. BlockIO should be a value between 10 and 1000. Please see this documentation for more information about it.

    -

    -
    -
    -
    -
    -
    -
    - -
    - -
    - -

    Select the type of service that this server will be running.

    -
    -
    - - -
    -
    -
    -
    -
    -
    -
    - -
    - - - - -
    -

    If you would like to use a custom docker image for this server please enter it here. Most users can ignore this option.

    -
    -
    -
    -
    -
    - -
    -
    -
    - {!! csrf_field() !!} - -
    -
    -
    -
    -
    - -@endsection diff --git a/resources/views/admin/settings.blade.php b/resources/views/admin/settings.blade.php deleted file mode 100644 index 09e66d6fc..000000000 --- a/resources/views/admin/settings.blade.php +++ /dev/null @@ -1,97 +0,0 @@ -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} -@extends('layouts.admin') - -@section('title') - Administration -@endsection - -@section('content') -
    - -

    Panel Settings


    -
    -
    -
    - -
    - -

    This is the name that is used throughout the panel and in emails sent to clients.

    -
    -
    -
    - -
    - -

    This is the default language that all clients will use unless they manually change it.

    -
    -
    -
    -
    -
    -
    In order to modify your SMTP settings for sending mail you will need to edit the .env file in this project's root folder.
    -
    -
    -
    -
    - -
    - -

    The email address that panel emails will be sent from. Note that some SMTP services require this to match for a given API key.

    -
    -
    -
    - -
    - -

    The name that emails will appear to come from.

    -
    -
    -
    -
    -
    -
    - {!! csrf_field() !!} - -
    -
    -
    -
    -
    - -@endsection diff --git a/resources/views/admin/users/index.blade.php b/resources/views/admin/users/index.blade.php deleted file mode 100644 index 7e85c8fe9..000000000 --- a/resources/views/admin/users/index.blade.php +++ /dev/null @@ -1,73 +0,0 @@ -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Some Modifications (c) 2015 Dylan Seidt --}} - -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} -@extends('layouts.admin') - -@section('title') - Account List -@endsection - -@section('content') -
    - -

    All Registered Users


    -
    -
    - -
    - -
    -
    -
    - - - - - - - - - - @foreach ($users as $user) - - - - - - - - @endforeach - -
    ID - Email - Client NameUsername
    #{{ $user->id }}{{ $user->email }}{{ $user->name_last }}, {{ $user->name_first }}{{ $user->username }}
    -
    -
    {!! $users->render() !!}
    -
    -
    - -@endsection diff --git a/resources/views/admin/users/new.blade.php b/resources/views/admin/users/new.blade.php deleted file mode 100644 index 8e0bf5f7a..000000000 --- a/resources/views/admin/users/new.blade.php +++ /dev/null @@ -1,132 +0,0 @@ -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} -{{-- Some Modifications (c) 2015 Dylan Seidt --}} - -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} -@extends('layouts.admin') - -@section('title') - New Account -@endsection - -@section('content') -
    - -

    Create New Account


    -
    -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -

    Setting this to 'Yes' gives a user full administrative access.

    -
    -
    -
    -
    -
    -
    -
    -

    Providing a user password is optional. New user emails prompt users to create a password the first time they login. If a password is provided here you will need to find a different method of providing it to the user.

    -
    -
    -
    - -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    -
    -
    - {!! csrf_field() !!} - - -
    -
    -
    -
    -
    - -@endsection diff --git a/resources/views/layouts/admin.blade.php b/resources/views/layouts/admin.blade.php index f21e1a0c7..f800c1ac5 100644 --- a/resources/views/layouts/admin.blade.php +++ b/resources/views/layouts/admin.blade.php @@ -212,10 +212,6 @@