Merge pull request #334 from Pterodactyl/feature/admin-retheme
This PR purposely leaves out services from the Admin CP as they will be re-implemented in another branch due to the sheer amount of code that needs to be adjusted and worked with.
This commit is contained in:
commit
a5577f0afd
18
CHANGELOG.md
18
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.
|
||||
|
|
|
@ -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'));
|
||||
}
|
||||
|
||||
public function getScript(Request $request, $id)
|
||||
{
|
||||
return response()->view('admin.nodes.remote.deploy', [
|
||||
'node' => Models\Node::findOrFail($id),
|
||||
])->header('Content-Type', 'text/plain');
|
||||
return view('admin.nodes.index', ['nodes' => $nodes->paginate(25)]);
|
||||
}
|
||||
|
||||
public function getIndex(Request $request)
|
||||
/**
|
||||
* Displays create new node page.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \Illuminate\View\View|\Illuminate\Response\RedirectResponse
|
||||
*/
|
||||
public function new(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. <strong>Before you can add any servers you need to first assign some IP addresses and ports.</strong>')->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 <code>' . $request->input('ip') . '</code>.')->flash();
|
||||
|
||||
return redirect()->route('admin.nodes.view', [
|
||||
'id' => $node,
|
||||
'tab' => 'tab_allocation',
|
||||
]);
|
||||
}
|
||||
|
||||
public function setAlias(Request $request, $node)
|
||||
return redirect()->route('admin.nodes.view.allocation', $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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteNode(Request $request, $id)
|
||||
{
|
||||
try {
|
||||
$repo = new NodeRepository;
|
||||
|
||||
try {
|
||||
$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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->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]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
$repo->$action($id);
|
||||
|
||||
Alert::success('Server has been ' . $action . 'ed.');
|
||||
} catch (DisplayException $ex) {
|
||||
Alert::danger($ex->getMessage())->flash();
|
||||
} catch (\Exception $ex) {
|
||||
Log::error($ex);
|
||||
Alert::danger('An unhandled exception occured while attemping to ' . $action . ' this server. This error has been logged.')->flash();
|
||||
}
|
||||
|
||||
return redirect()->route('admin.servers.view.manage', $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
$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', [
|
||||
'id' => $id,
|
||||
'tab' => 'tab_build',
|
||||
])->withErrors(json_decode($ex->getMessage()))->withInput();
|
||||
return redirect()->route('admin.servers.view.build', $id)->withErrors(json_decode($ex->getMessage()))->withInput();
|
||||
} 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 add 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.build', $id);
|
||||
}
|
||||
|
||||
public function deleteServer(Request $request, $id, $force = null)
|
||||
/**
|
||||
* 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 {
|
||||
$server = new ServerRepository;
|
||||
$server->deleteServer($id, $force);
|
||||
$repo->queueDeletion($id, ($request->input('is_force') > 0));
|
||||
Alert::success('Server has been marked for deletion on the system.')->flash();
|
||||
|
||||
return redirect()->route('admin.servers');
|
||||
} 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. 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)
|
||||
/**
|
||||
* Cancels a pending server deletion request.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param int $id
|
||||
* @return \Illuminate\Response\RedirectResponse
|
||||
*/
|
||||
public function cancelDeletion(Request $request, $id)
|
||||
{
|
||||
try {
|
||||
$server = new ServerRepository;
|
||||
$server->toggleInstall($id);
|
||||
Alert::success('Server 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.')->flash();
|
||||
} finally {
|
||||
return redirect()->route('admin.servers.view', [
|
||||
'id' => $id,
|
||||
'tab' => 'tab_manage',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function postUpdateServerStartup(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();
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
} 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);
|
||||
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');
|
||||
} 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');
|
||||
}
|
||||
} catch (DisplayException $ex) {
|
||||
Alert::danger($ex->getMessage())->flash();
|
||||
|
||||
return redirect()->route('admin.servers.view', $id);
|
||||
} 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 error occured while attempting to perform this action.')->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);
|
||||
return redirect()->route('admin.servers.view.delete', $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 {
|
||||
$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 update startup variables for this server. This error has been logged.')->flash();
|
||||
}
|
||||
|
||||
return redirect()->route('admin.servers.view.startup', $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)
|
||||
{
|
||||
$repo = new DatabaseRepository;
|
||||
|
||||
try {
|
||||
$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.database', $id)->withInput()->withErrors(json_decode($ex->getMessage()))->withInput();
|
||||
} catch (DisplayException $ex) {
|
||||
Alert::danger($ex->getMessage())->flash();
|
||||
} catch (\Exception $ex) {
|
||||
Log::error($ex);
|
||||
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.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
try {
|
||||
$database = Models\Database::where('server_id', $server->id)->findOrFail($request->input('database'));
|
||||
$repo = new Repositories\DatabaseRepository;
|
||||
$password = str_random(16);
|
||||
$repo->modifyPassword($request->input('database'), $password);
|
||||
|
||||
try {
|
||||
$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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
|
@ -58,6 +58,6 @@ class DeleteServer extends Job implements ShouldQueue
|
|||
public function handle()
|
||||
{
|
||||
$repo = new ServerRepository;
|
||||
$repo->deleteNow($this->id);
|
||||
$repo->delete($this->id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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:<uuid>
|
||||
* 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));
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
} 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);
|
||||
if ($database->exists) {
|
||||
throw new DisplayException('A database with those details already exists in the system.');
|
||||
}
|
||||
|
||||
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::commit();
|
||||
$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) {
|
||||
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;
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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,8 +137,7 @@ 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,
|
||||
|
@ -137,23 +146,16 @@ class DatabaseRepository
|
|||
'password' => Crypt::decrypt($database->host->password),
|
||||
'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,10 +170,8 @@ 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,
|
||||
|
@ -180,22 +180,15 @@ class DatabaseRepository
|
|||
'password' => Crypt::decrypt($database->host->password),
|
||||
'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,10 +236,8 @@ class DatabaseRepository
|
|||
}
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
try {
|
||||
$capsule = new Capsule;
|
||||
$capsule->addConnection([
|
||||
Config::set('database.connections.dynamic', [
|
||||
'driver' => 'mysql',
|
||||
'host' => $data['host'],
|
||||
'port' => $data['port'],
|
||||
|
@ -255,16 +246,10 @@ class DatabaseRepository
|
|||
'password' => $data['password'],
|
||||
'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'],
|
||||
|
|
|
@ -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();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
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)) {
|
||||
$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,
|
||||
$validator = Validator::make($data, [
|
||||
'allocation_ip' => 'required|string',
|
||||
'allocation_alias' => 'sometimes|required|string|max:255',
|
||||
'allocation_ports' => 'required|array',
|
||||
]);
|
||||
if (! $alloc->exists) {
|
||||
$alloc->fill([
|
||||
|
||||
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 <code>' . $port . '</code> 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.');
|
||||
}
|
||||
|
||||
foreach ($block as $unit) {
|
||||
// Insert into Database
|
||||
Models\Allocation::firstOrCreate([
|
||||
'node_id' => $node->id,
|
||||
'ip' => $ip,
|
||||
'port' => $assignPort,
|
||||
'ip_alias' => $setAlias,
|
||||
'port' => $unit,
|
||||
'ip_alias' => isset($data['allocation_alias']) ? $data['allocation_alias'] : null,
|
||||
'server_id' => null,
|
||||
]);
|
||||
$alloc->save();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$alloc = Models\Allocation::firstOrNew([
|
||||
// Insert into Database
|
||||
Models\Allocation::firstOrCreate([
|
||||
'node_id' => $node->id,
|
||||
'ip' => $ip,
|
||||
'port' => $port,
|
||||
]);
|
||||
if (! $alloc->exists) {
|
||||
$alloc->fill([
|
||||
'node_id' => $node->id,
|
||||
'ip' => $ip,
|
||||
'port' => $port,
|
||||
'ip_alias' => $setAlias,
|
||||
'ip_alias' => isset($data['allocation_alias']) ? $data['allocation_alias'] : null,
|
||||
'server_id' => null,
|
||||
]);
|
||||
$alloc->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
} catch (\Exception $ex) {
|
||||
DB::rollBack();
|
||||
throw $ex;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function delete($id)
|
||||
|
|
|
@ -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,118 +617,84 @@ 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.');
|
||||
}
|
||||
|
||||
// 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],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Add Variables
|
||||
$environmentVariables = [
|
||||
'STARTUP' => $server->startup,
|
||||
];
|
||||
foreach ($variableList as $item) {
|
||||
$environmentVariables[$item['env']] = $item['val'];
|
||||
|
||||
// Update model or make a new record if it doesn't exist.
|
||||
$model = Models\ServerVariable::firstOrNew([
|
||||
'variable_id' => $item['id'],
|
||||
$svar = Models\ServerVariable::firstOrNew([
|
||||
'server_id' => $server->id,
|
||||
'variable_id' => $variable->id,
|
||||
]);
|
||||
$model->variable_value = $item['val'];
|
||||
$model->save();
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
$svar->save();
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
return [
|
||||
'variable' => $item->env_variable,
|
||||
'value' => (! is_null($display)) ? $display : $item->default_value,
|
||||
];
|
||||
});
|
||||
|
||||
$server->node->guzzleClient([
|
||||
'X-Access-Server' => $server->uuid,
|
||||
'X-Access-Token' => $server->node->daemonSecret,
|
||||
])->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;
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -15,6 +15,7 @@ return [
|
|||
*/
|
||||
'bind_js_vars_to_this_view' => [
|
||||
'layouts.master',
|
||||
'layouts.admin',
|
||||
],
|
||||
|
||||
/*
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class UpdateNodeConfigTokensColumns extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('node_configuration_tokens', function (Blueprint $table) {
|
||||
$table->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');
|
||||
});
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>
|
||||
//
|
||||
// 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');}
|
|
@ -0,0 +1,195 @@
|
|||
// Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>
|
||||
//
|
||||
// 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 '<div class="user-block"> \
|
||||
<img class="img-circle img-bordered-xs" src="https://www.gravatar.com/avatar/' + data.md5 + '?s=120" alt="User Image"> \
|
||||
<span class="username"> \
|
||||
<a href="#">' + data.name_first + ' ' + data.name_last +'</a> \
|
||||
</span> \
|
||||
<span class="description"><strong>' + data.email + '</strong> - ' + data.username + '</span> \
|
||||
</div>';
|
||||
},
|
||||
templateSelection: function (data) {
|
||||
return '<div> \
|
||||
<span> \
|
||||
<img class="img-rounded img-bordered-xs" src="https://www.gravatar.com/avatar/' + data.md5 + '?s=120" style="height:28px;margin-top:-4px;" alt="User Image"> \
|
||||
</span> \
|
||||
<span style="padding-left:5px;"> \
|
||||
' + data.name_first + ' ' + data.name_last + ' (<strong>' + data.email + '</strong>) \
|
||||
</span> \
|
||||
</div>';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
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) ? '<span class="label label-danger">Required</span> ' : '';
|
||||
var dataAppend = ' \
|
||||
<div class="form-group col-sm-6"> \
|
||||
<label for="var_ref_' + item.id + '" class="control-label">' + isRequired + item.name + '</label> \
|
||||
<input type="text" id="var_ref_' + item.id + '" autocomplete="off" name="env_' + item.env_variable + '" class="form-control" value="' + item.default_value + '" /> \
|
||||
<p class="text-muted small">' + item.description + '<br /> \
|
||||
<strong>Access in Startup:</strong> <code>@{{' + item.env_variable + '}}</code><br /> \
|
||||
<strong>Validation Regex:</strong> <code>' + item.regex + '</code></small></p> \
|
||||
</div> \
|
||||
';
|
||||
$('#appendVariablesTo').append(dataAppend);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,111 @@
|
|||
// Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>
|
||||
//
|
||||
// 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.<br /><br />' + 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('<span class="label label-danger">Offline</span>');
|
||||
break;
|
||||
case 1:
|
||||
element.find('[data-action="status"]').html('<span class="label label-success">Online</span>');
|
||||
break;
|
||||
case 2:
|
||||
element.find('[data-action="status"]').html('<span class="label label-info">Starting</span>');
|
||||
break;
|
||||
case 3:
|
||||
element.find('[data-action="status"]').html('<span class="label label-info">Stopping</span>');
|
||||
break;
|
||||
case 20:
|
||||
element.find('[data-action="status"]').html('<span class="label label-warning">Installing</span>');
|
||||
break;
|
||||
case 30:
|
||||
element.find('[data-action="status"]').html('<span class="label label-warning">Suspended</span>');
|
||||
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('--');
|
||||
}
|
||||
});
|
||||
});
|
||||
})();
|
|
@ -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();
|
||||
|
|
|
@ -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('<span class="label label-default">Error</span>');
|
||||
});
|
||||
});
|
||||
}).promise().done(function () {
|
||||
setTimeout(updateServerStatus, 10000);
|
||||
}
|
||||
|
||||
return {
|
||||
init: function () {
|
||||
updateServerStatus();
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
})();
|
||||
|
||||
ServerList.init();
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -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<i;){var o=t[u];n(e,o,r(o),t)}return e}function u(t,n){for(var r=-1,e=t?t.length:0;++r<e&&false!==n(t[r],r,t););return t}function i(t,n){for(var r=t?t.length:0;r--&&false!==n(t[r],r,t););
|
||||
return t}function o(t,n){for(var r=-1,e=t?t.length:0;++r<e;)if(!n(t[r],r,t))return false;return true}function f(t,n){for(var r=-1,e=t?t.length:0,u=0,i=[];++r<e;){var o=t[r];n(o,r,t)&&(i[u++]=o)}return i}function c(t,n){return!(!t||!t.length)&&-1<d(t,n,0)}function a(t,n,r){for(var e=-1,u=t?t.length:0;++e<u;)if(r(n,t[e]))return true;return false}function l(t,n){for(var r=-1,e=t?t.length:0,u=Array(e);++r<e;)u[r]=n(t[r],r,t);return u}function s(t,n){for(var r=-1,e=n.length,u=t.length;++r<e;)t[u+r]=n[r];return t}function h(t,n,r,e){
|
||||
var u=-1,i=t?t.length:0;for(e&&i&&(r=t[++u]);++u<i;)r=n(r,t[u],u,t);return r}function p(t,n,r,e){var u=t?t.length:0;for(e&&u&&(r=t[--u]);u--;)r=n(r,t[u],u,t);return r}function _(t,n){for(var r=-1,e=t?t.length:0;++r<e;)if(n(t[r],r,t))return true;return false}function v(t,n,r){var e;return r(t,function(t,r,u){if(n(t,r,u))return e=r,false}),e}function g(t,n,r,e){var u=t.length;for(r+=e?1:-1;e?r--:++r<u;)if(n(t[r],r,t))return r;return-1}function d(t,n,r){if(n===n)t:{--r;for(var e=t.length;++r<e;)if(t[r]===n){t=r;
|
||||
break t}t=-1}else t=g(t,b,r);return t}function y(t,n,r,e){--r;for(var u=t.length;++r<u;)if(e(t[r],n))return r;return-1}function b(t){return t!==t}function x(t,n){var r=t?t.length:0;return r?k(t,n)/r:P}function j(t){return function(n){return null==n?F:n[t]}}function w(t){return function(n){return null==t?F:t[n]}}function m(t,n,r,e,u){return u(t,function(t,u,i){r=e?(e=false,t):n(r,t,u,i)}),r}function A(t,n){var r=t.length;for(t.sort(n);r--;)t[r]=t[r].c;return t}function k(t,n){for(var r,e=-1,u=t.length;++e<u;){
|
||||
var i=n(t[e]);i!==F&&(r=r===F?i:r+i)}return r}function E(t,n){for(var r=-1,e=Array(t);++r<t;)e[r]=n(r);return e}function O(t,n){return l(n,function(n){return[n,t[n]]})}function S(t){return function(n){return t(n)}}function I(t,n){return l(n,function(n){return t[n]})}function R(t,n){return t.has(n)}function z(t,n){for(var r=-1,e=t.length;++r<e&&-1<d(n,t[r],0););return r}function W(t,n){for(var r=t.length;r--&&-1<d(n,t[r],0););return r}function B(t){return"\\"+Dt[t]}function L(t){var n=-1,r=Array(t.size);
|
||||
return t.forEach(function(t,e){r[++n]=[e,t]}),r}function U(t,n){return function(r){return t(n(r))}}function C(t,n){for(var r=-1,e=t.length,u=0,i=[];++r<e;){var o=t[r];o!==n&&"__lodash_placeholder__"!==o||(t[r]="__lodash_placeholder__",i[u++]=r)}return i}function M(t){var n=-1,r=Array(t.size);return t.forEach(function(t){r[++n]=t}),r}function D(t){var n=-1,r=Array(t.size);return t.forEach(function(t){r[++n]=[t,t]}),r}function T(t){if(Wt.test(t)){for(var n=Rt.lastIndex=0;Rt.test(t);)++n;t=n}else t=tn(t);
|
||||
return t}function $(t){return Wt.test(t)?t.match(Rt)||[]:t.split("")}var F,N=1/0,P=NaN,Z=[["ary",128],["bind",1],["bindKey",2],["curry",8],["curryRight",16],["flip",512],["partial",32],["partialRight",64],["rearg",256]],q=/\b__p\+='';/g,V=/\b(__p\+=)''\+/g,K=/(__e\(.*?\)|\b__t\))\+'';/g,G=/&(?:amp|lt|gt|quot|#39);/g,J=/[&<>"']/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<r;){var e=t[n];this.set(e[0],e[1])}}function Nt(t){var n=-1,r=t?t.length:0;for(this.clear();++n<r;){var e=t[n];this.set(e[0],e[1])}}function Zt(t){var n=-1,r=t?t.length:0;for(this.clear();++n<r;){var e=t[n];this.set(e[0],e[1])}}function qt(t){var n=-1,r=t?t.length:0;for(this.__data__=new Zt;++n<r;)this.add(t[n])}function Kt(t){this.size=(this.__data__=new Nt(t)).size;
|
||||
}function tn(t,n){var r,e=tf(t)||ou(t)?E(t.length,Yu):[],u=e.length,i=!!u;for(r in t)!n&&!ei.call(t,r)||i&&("length"==r||je(r,u))||e.push(r);return e}function on(t){var n=t.length;return n?t[cr(0,n-1)]:F}function fn(t,n){return We(Ur(t),n)}function cn(t){return We(Ur(t))}function an(t,n,r,e){return t===F||iu(t,Xu[r])&&!ei.call(e,r)?n:t}function ln(t,n,r){(r===F||iu(t[n],r))&&(typeof n!="number"||r!==F||n in t)||vn(t,n,r)}function sn(t,n,r){var e=t[n];ei.call(t,n)&&iu(e,r)&&(r!==F||n in t)||vn(t,n,r);
|
||||
}function hn(t,n){for(var r=t.length;r--;)if(iu(t[r][0],n))return r;return-1}function pn(t,n,r,e){return to(t,function(t,u,i){n(e,t,r(t),i)}),e}function _n(t,n){return t&&Cr(n,Su(n),t)}function vn(t,n,r){"__proto__"==n&&pi?pi(t,n,{configurable:true,enumerable:true,value:r,writable:true}):t[n]=r}function gn(t,n){for(var r=-1,e=null==t,u=n.length,i=Pu(u);++r<u;)i[r]=e?F:Eu(t,n[r]);return i}function dn(t,n,r){return t===t&&(r!==F&&(t=t<=r?t:r),n!==F&&(t=t>=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(;++u<f;){var p=t[u],_=r?r(p):p,p=e||0!==p?p:0;if(o&&_===_){for(var v=h;v--;)if(n[v]===_)continue t;
|
||||
s.push(p)}else i(n,_,e)||s.push(p)}return s}function mn(t,n){var r=true;return to(t,function(t,e,u){return r=!!n(t,e,u)}),r}function An(t,n,r){for(var e=-1,u=t.length;++e<u;){var i=t[e],o=n(i);if(null!=o&&(f===F?o===o&&!yu(o):r(o,f)))var f=o,c=i}return c}function kn(t,n){var r=[];return to(t,function(t,e,u){n(t,e,u)&&r.push(t)}),r}function En(t,n,r,e,u){var i=-1,o=t.length;for(r||(r=xe),u||(u=[]);++i<o;){var f=t[i];0<n&&r(f)?1<n?En(f,n-1,r,e,u):s(u,f):e||(u[u.length]=f)}return u}function On(t,n){return t&&ro(t,n,Su);
|
||||
}function Sn(t,n){return t&&eo(t,n,Su)}function In(t,n){return f(n,function(n){return lu(t[n])})}function Rn(t,n){n=me(n,t)?[n]:Sr(n);for(var r=0,e=n.length;null!=t&&r<e;)t=t[Be(n[r++])];return r&&r==e?t:F}function zn(t,n,r){return n=n(t),tf(t)?n:s(n,r(t))}function Wn(t,n){return t>n}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(;++_<u&&h.length<s;){var g=p[_],d=n?n(g):g,g=r||0!==g?g:0;if(v?!R(v,d):!e(h,d,r)){for(o=i;--o;){var y=f[o];if(y?!R(y,d):!e(t[o],d,r))continue t}v&&v.push(d),h.push(g)}}return h}function Cn(t,n,r){var e={};return On(t,function(t,u,i){n(e,r(t),u,i)}),e}function Mn(t,n,e){return me(n,t)||(n=Sr(n),t=Ie(t,n),n=Fe(n)),n=null==t?t:t[Be(n)],null==n?F:r(n,t,e)}function Dn(t){return _u(t)&&"[object ArrayBuffer]"==oi.call(t)}function Tn(t){
|
||||
return _u(t)&&"[object Date]"==oi.call(t)}function $n(t,n,r,e,u){if(t===n)n=true;else if(null==t||null==n||!pu(t)&&!_u(n))n=t!==t&&n!==n;else t:{var i=tf(t),o=tf(n),f="[object Array]",c="[object Array]";i||(f=Et(t),f="[object Arguments]"==f?"[object Object]":f),o||(c=Et(n),c="[object Arguments]"==c?"[object Object]":c);var a="[object Object]"==f,o="[object Object]"==c;if((c=f==c)&&!a)u||(u=new Kt),n=i||cf(t)?fe(t,n,$n,r,e,u):ce(t,n,f,$n,r,e,u);else{if(!(2&e)&&(i=a&&ei.call(t,"__wrapped__"),f=o&&ei.call(n,"__wrapped__"),
|
||||
i||f)){t=i?t.value():t,n=f?n.value():n,u||(u=new Kt),n=$n(t,n,r,e,u);break t}if(c)n:if(u||(u=new Kt),i=2&e,f=Su(t),o=f.length,c=Su(n).length,o==c||i){for(a=o;a--;){var l=f[a];if(!(i?l in n:ei.call(n,l))){n=false;break n}}if((c=u.get(t))&&u.get(n))n=c==n;else{c=true,u.set(t,n),u.set(n,t);for(var s=i;++a<o;){var l=f[a],h=t[l],p=n[l];if(r)var _=i?r(p,h,l,n,t,u):r(h,p,l,t,n,u);if(_===F?h!==p&&!$n(h,p,r,e,u):!_){c=false;break}s||(s="constructor"==l)}c&&!s&&(r=t.constructor,e=n.constructor,r!=e&&"constructor"in t&&"constructor"in n&&!(typeof r=="function"&&r instanceof r&&typeof e=="function"&&e instanceof e)&&(c=false)),
|
||||
u.delete(t),u.delete(n),n=c}}else n=false;else n=false}}return n}function Fn(t){return _u(t)&&"[object Map]"==Et(t)}function Nn(t,n,r,e){var u=r.length,i=u,o=!e;if(null==t)return!i;for(t=Gu(t);u--;){var f=r[u];if(o&&f[2]?f[1]!==t[f[0]]:!(f[0]in t))return false}for(;++u<i;){var f=r[u],c=f[0],a=t[c],l=f[1];if(o&&f[2]){if(a===F&&!(c in t))return false}else{if(f=new Kt,e)var s=e(a,l,c,t,n,f);if(s===F?!$n(l,a,e,3,f):!s)return false}}return true}function Pn(t){return!(!pu(t)||ni&&ni in t)&&(lu(t)?ci:bt).test(Le(t))}function Zn(t){
|
||||
return pu(t)&&"[object RegExp]"==oi.call(t)}function qn(t){return _u(t)&&"[object Set]"==Et(t)}function Vn(t){return _u(t)&&hu(t.length)&&!!Ut[oi.call(t)]}function Kn(t){return typeof t=="function"?t:null==t?Cu:typeof t=="object"?tf(t)?Xn(t[0],t[1]):Qn(t):$u(t)}function Gn(t){if(!ke(t))return Ii(t);var n,r=[];for(n in Gu(t))ei.call(t,n)&&"constructor"!=n&&r.push(n);return r}function Jn(t){if(!pu(t)){var n=[];if(null!=t)for(var r in Gu(t))n.push(r);return n}r=ke(t);var e=[];for(n in t)("constructor"!=n||!r&&ei.call(t,n))&&e.push(n);
|
||||
return e}function Yn(t,n){return t<n}function Hn(t,n){var r=-1,e=fu(t)?Pu(t.length):[];return to(t,function(t,u,i){e[++r]=n(t,u,i)}),e}function Qn(t){var n=_e(t);return 1==n.length&&n[0][2]?Ee(n[0][0],n[0][1]):function(r){return r===t||Nn(r,t,n)}}function Xn(t,n){return me(t)&&n===n&&!pu(n)?Ee(Be(t),n):function(r){var e=Eu(r,t);return e===F&&e===n?Ou(r,t):$n(n,e,F,3)}}function tr(t,n,r,e,i){if(t!==n){if(!tf(n)&&!cf(n))var o=Jn(n);u(o||n,function(u,f){if(o&&(f=u,u=n[f]),pu(u)){i||(i=new Kt);var c=f,a=i,l=t[c],s=n[c],h=a.get(s);
|
||||
if(h)ln(t,c,h);else{var h=e?e(l,s,c+"",t,n,a):F,p=h===F;p&&(h=s,tf(s)||cf(s)?tf(l)?h=l:cu(l)?h=Ur(l):(p=false,h=yn(s,true)):gu(s)||ou(s)?ou(l)?h=Au(l):!pu(l)||r&&lu(l)?(p=false,h=yn(s,true)):h=l:p=false),p&&(a.set(s,h),tr(h,s,r,e,a),a.delete(s)),ln(t,c,h)}}else c=e?e(t[f],u,f+"",t,n,i):F,c===F&&(c=u),ln(t,f,c)})}}function nr(t,n){var r=t.length;if(r)return n+=0>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<o;){var c=Wr(u[e],i[e]);if(c){e=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={};++e<u;){var o=n[e],f=t[o];r(f,o)&&vn(i,o,f)}return i}function ir(t){return function(n){return Rn(n,t)}}function or(t,n,r,e){var u=e?y:d,i=-1,o=n.length,f=t;for(t===n&&(n=Ur(n)),r&&(f=l(t,S(r)));++i<o;)for(var c=0,a=n[i],a=r?r(a):a;-1<(c=u(f,a,c,e));)f!==t&&yi.call(f,c,1),
|
||||
yi.call(t,c,1);return t}function fr(t,n){for(var r=t?n.length:0,e=r-1;r--;){var u=n[r];if(r==e||u!==i){var i=u;if(je(u))yi.call(t,u,1);else if(me(u,t))delete t[Be(u)];else{var u=Sr(u),o=Ie(t,u);null!=o&&delete o[Be(Fe(u))]}}}}function cr(t,n){return t+Ai(Li()*(n-t+1))}function ar(t,n){var r="";if(!t||1>n||9007199254740991<n)return r;do n%2&&(r+=t),(n=Ai(n/2))&&(t+=t);while(n);return r}function lr(t,n){return _o(Se(t,n,Cu),t+"")}function sr(t){return on(zu(t))}function hr(t,n){return We(zu(t),n)}function pr(t,n,r,e){
|
||||
if(!pu(t))return t;n=me(n,t)?[n]:Sr(n);for(var u=-1,i=n.length,o=i-1,f=t;null!=f&&++u<i;){var c=Be(n[u]),a=r;if(u!=o){var l=f[c],a=e?e(l,c,f):F;a===F&&(a=pu(l)?l:je(n[u+1])?[]:{})}sn(f,c,a),f=f[c]}return t}function _r(t){return We(zu(t))}function vr(t,n,r){var e=-1,u=t.length;for(0>n&&(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;)r[e]=t[e+n];return r}function gr(t,n){var r;return to(t,function(t,e,u){return r=n(t,e,u),!r}),!!r}function dr(t,n,r){var e=0,u=t?t.length:e;
|
||||
if(typeof n=="number"&&n===n&&2147483647>=u){for(;e<u;){var i=e+u>>>1,o=t[i];null!==o&&!yu(o)&&(r?o<=n:o<n)?e=i+1:u=i}return u}return yr(t,n,Cu,r)}function yr(t,n,r,e){n=r(n);for(var u=0,i=t?t.length:0,o=n!==n,f=null===n,c=yu(n),a=n===F;u<i;){var l=Ai((u+i)/2),s=r(t[l]),h=s!==F,p=null===s,_=s===s,v=yu(s);(o?e||_:a?_&&(e||h):f?_&&h&&(e||!p):c?_&&h&&!p&&(e||!v):p||v?0:e?s<=n:s<n)?u=l+1:i=l}return zi(i,4294967294)}function br(t,n){for(var r=-1,e=t.length,u=0,i=[];++r<e;){var o=t[r],f=n?n(o):o;if(!r||!iu(f,c)){
|
||||
var c=f;i[u++]=0===o?0:o}}return i}function xr(t){return typeof t=="number"?t:yu(t)?P:+t}function jr(t){if(typeof t=="string")return t;if(yu(t))return Qi?Qi.call(t):"";var n=t+"";return"0"==n&&1/t==-N?"-0":n}function wr(t,n,r){var e=-1,u=c,i=t.length,o=true,f=[],l=f;if(r)o=false,u=a;else if(200<=i){if(u=n?null:fo(t))return M(u);o=false,u=R,l=new qt}else l=n?[]:f;t:for(;++e<i;){var s=t[e],h=n?n(s):s,s=r||0!==s?s:0;if(o&&h===h){for(var p=l.length;p--;)if(l[p]===h)continue t;n&&l.push(h),f.push(s)}else u(l,h,r)||(l!==f&&l.push(h),
|
||||
f.push(s))}return f}function mr(t,n,r,e){for(var u=t.length,i=e?u:-1;(e?i--:++i<u)&&n(t[i],i,t););return r?vr(t,e?0:i,e?i+1:u):vr(t,e?i+1:0,e?u:i)}function Ar(t,n){var r=t;return r instanceof Dt&&(r=r.value()),h(n,function(t,n){return n.func.apply(n.thisArg,s([t],n.args))},r)}function kr(t,n,r){for(var e=-1,u=t.length;++e<u;)var i=i?s(wn(i,t[e],n,r),wn(t[e],i,n,r)):t[e];return i&&i.length?wr(i,n,r):[]}function Er(t,n,r){for(var e=-1,u=t.length,i=n.length,o={};++e<u;)r(o,t[e],e<i?n[e]:F);return o}
|
||||
function Or(t){return cu(t)?t:[]}function Sr(t){return tf(t)?t:vo(t)}function Ir(t,n,r){var e=t.length;return r=r===F?e:r,!n&&r>=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&&t<n||a&&r&&u&&!e&&!i||f&&r&&u||!o&&u||!c)return-1;
|
||||
}return 0}function Br(t,n,r,e){var u=-1,i=t.length,o=r.length,f=-1,c=n.length,a=Ri(i-o,0),l=Pu(c+a);for(e=!e;++f<c;)l[f]=n[f];for(;++u<o;)(e||u<i)&&(l[r[u]]=t[u]);for(;a--;)l[f++]=t[u++];return l}function Lr(t,n,r,e){var u=-1,i=t.length,o=-1,f=r.length,c=-1,a=n.length,l=Ri(i-f,0),s=Pu(l+a);for(e=!e;++u<l;)s[u]=t[u];for(l=u;++c<a;)s[l+c]=n[c];for(;++o<f;)(e||u<i)&&(s[l+r[o]]=t[u++]);return s}function Ur(t,n){var r=-1,e=t.length;for(n||(n=Pu(e));++r<e;)n[r]=t[r];return n}function Cr(t,n,r,e){var u=!r;
|
||||
r||(r={});for(var i=-1,o=n.length;++i<o;){var f=n[i],c=e?e(r[f],t[f],f,r,t):F;c===F&&(c=t[f]),u?vn(r,f,c):sn(r,f,c)}return r}function Mr(t,n){return Cr(t,ao(t),n)}function Dr(t,n){return function(r,u){var i=tf(r)?e:pn,o=n?n():{};return i(r,t,he(u,2),o)}}function Tr(t){return lr(function(n,r){var e=-1,u=r.length,i=1<u?r[u-1]:F,o=2<u?r[2]:F,i=3<t.length&&typeof i=="function"?(u--,i):F;for(o&&we(r[0],r[1],o)&&(i=3>u?F:i,u=1),n=Gu(n);++e<u;)(o=r[e])&&t(n,o,e,i);return n})}function $r(t,n){return function(r,e){
|
||||
if(null==r)return r;if(!fu(r))return t(r,e);for(var u=r.length,i=n?u:-1,o=Gu(r);(n?i--:++i<u)&&false!==e(o[i],i,o););return r}}function Fr(t){return function(n,r,e){var u=-1,i=Gu(n);e=e(n);for(var o=e.length;o--;){var f=e[t?o:++u];if(false===r(i[f],f,i))break}return n}}function Nr(t,n,r){function e(){return(this&&this!==Pt&&this instanceof e?i:t).apply(u?r:this,arguments)}var u=1&n,i=qr(t);return e}function Pr(t){return function(n){n=ku(n);var r=Wt.test(n)?$(n):F,e=r?r[0]:n.charAt(0);return n=r?Ir(r,1).join(""):n.slice(1),
|
||||
e[t]()+n}}function Zr(t){return function(n){return h(Lu(Bu(n).replace(St,"")),t,"")}}function qr(t){return function(){var n=arguments;switch(n.length){case 0:return new t;case 1:return new t(n[0]);case 2:return new t(n[0],n[1]);case 3:return new t(n[0],n[1],n[2]);case 4:return new t(n[0],n[1],n[2],n[3]);case 5:return new t(n[0],n[1],n[2],n[3],n[4]);case 6:return new t(n[0],n[1],n[2],n[3],n[4],n[5]);case 7:return new t(n[0],n[1],n[2],n[3],n[4],n[5],n[6])}var r=Xi(t.prototype),n=t.apply(r,n);return pu(n)?n:r;
|
||||
}}function Vr(t,n,e){function u(){for(var o=arguments.length,f=Pu(o),c=o,a=se(u);c--;)f[c]=arguments[c];return c=3>o&&f[0]!==a&&f[o-1]!==a?[]:C(f,a),o-=c.length,o<e?ee(t,n,Jr,u.placeholder,F,f,c,F,F,e-o):r(this&&this!==Pt&&this instanceof u?i:t,this,f)}var i=qr(t);return u}function Kr(t){return function(n,r,e){var u=Gu(n);if(!fu(n)){var i=he(r,3);n=Su(n),r=function(t){return i(u[t],t,u)}}return r=t(n,r,e),-1<r?u[i?n[r]:r]:F}}function Gr(t){return ae(function(n){var r=n.length,e=r,u=Mt.prototype.thru;
|
||||
for(t&&n.reverse();e--;){var i=n[e];if(typeof i!="function")throw new Hu("Expected a function");if(u&&!o&&"wrapper"==le(i))var o=new Mt([],true)}for(e=o?e:r;++e<r;)var i=n[e],u=le(i),f="wrapper"==u?co(i):F,o=f&&Ae(f[0])&&424==f[1]&&!f[4].length&&1==f[9]?o[le(f[0])].apply(o,f[3]):1==i.length&&Ae(i)?o[u]():o.thru(i);return function(){var t=arguments,e=t[0];if(o&&1==t.length&&tf(e)&&200<=e.length)return o.plant(e).value();for(var u=0,t=r?n[u].apply(this,t):e;++u<r;)t=n[u].call(this,t);return t}})}function Jr(t,n,r,e,u,i,o,f,c,a){
|
||||
function l(){for(var d=arguments.length,y=Pu(d),b=d;b--;)y[b]=arguments[b];if(_){var x,j=se(l),b=y.length;for(x=0;b--;)y[b]===j&&++x}if(e&&(y=Br(y,e,u,_)),i&&(y=Lr(y,i,o,_)),d-=x,_&&d<a)return j=C(y,j),ee(t,n,Jr,l.placeholder,r,y,j,f,c,a-d);if(j=h?r:this,b=p?j[t]:t,d=y.length,f){x=y.length;for(var w=zi(f.length,x),m=Ur(y);w--;){var A=f[w];y[w]=je(A,x)?m[A]:F}}else v&&1<d&&y.reverse();return s&&c<d&&(y.length=c),this&&this!==Pt&&this instanceof l&&(b=g||qr(b)),b.apply(j,y)}var s=128&n,h=1&n,p=2&n,_=24&n,v=512&n,g=p?F:qr(t);
|
||||
return l}function Yr(t,n){return function(r,e){return Cn(r,t,n(e))}}function Hr(t,n){return function(r,e){var u;if(r===F&&e===F)return n;if(r!==F&&(u=r),e!==F){if(u===F)return e;typeof r=="string"||typeof e=="string"?(r=jr(r),e=jr(e)):(r=xr(r),e=xr(e)),u=t(r,e)}return u}}function Qr(t){return ae(function(n){return n=l(n,S(he())),lr(function(e){var u=this;return t(n,function(t){return r(t,u,e)})})})}function Xr(t,n){n=n===F?" ":jr(n);var r=n.length;return 2>r?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;++a<l;)s[a]=u[a];for(;c--;)s[a++]=arguments[++n];return r(h,o?e:this,s)}var o=1&n,f=qr(t);return i}function ne(t){return function(n,r,e){e&&typeof e!="number"&&we(n,r,e)&&(r=e=F),n=xu(n),r===F?(r=n,n=0):r=xu(r),e=e===F?n<r?1:-1:xu(e);var u=-1;r=Ri(mi((r-n)/(e||1)),0);for(var i=Pu(r);r--;)i[t?r:++u]=n,n+=e;return i}}function re(t){return function(n,r){return typeof n=="string"&&typeof r=="string"||(n=mu(n),
|
||||
r=mu(r)),t(n,r)}}function ee(t,n,r,e,u,i,o,f,c,a){var l=8&n,s=l?o:F;o=l?F:o;var h=l?i:F;return i=l?F:i,n=(n|(l?32:64))&~(l?64:32),4&n||(n&=-4),u=[t,n,u,h,s,i,o,f,c,a],r=r.apply(F,u),Ae(t)&&ho(r,u),r.placeholder=e,Re(r,t,n)}function ue(t){var n=Ku[t];return function(t,r){if(t=mu(t),r=zi(ju(r),292)){var e=(ku(t)+"e").split("e"),e=n(e[0]+"e"+(+e[1]+r)),e=(ku(e)+"e").split("e");return+(e[0]+"e"+(+e[1]-r))}return n(t)}}function ie(t){return function(n){var r=Et(n);return"[object Map]"==r?L(n):"[object Set]"==r?D(n):O(n,t(n));
|
||||
}}function oe(t,n,r,e,u,i,o,f){var c=2&n;if(!c&&typeof t!="function")throw new Hu("Expected a function");var a=e?e.length:0;if(a||(n&=-97,e=u=F),o=o===F?o:Ri(ju(o),0),f=f===F?f:ju(f),a-=u?u.length:0,64&n){var l=e,s=u;e=u=F}var h=c?F:co(t);return i=[t,n,r,e,u,l,s,i,o,f],h&&(r=i[1],t=h[1],n=r|t,e=128==t&&8==r||128==t&&256==r&&i[7].length<=h[8]||384==t&&h[7].length<=h[8]&&8==r,131>n||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);++c<f;){var s=t[c],h=n[c];if(e)var p=o?e(h,s,c,n,t,i):e(s,h,c,t,n,i);if(p!==F){if(p)continue;a=false;break}if(l){if(!_(n,function(t,n){if(!R(l,n)&&(s===t||r(s,t,e,u,i)))return l.push(n)})){a=false;break}}else if(s!==h&&!r(s,h,e,u,i)){a=false;break}}return i.delete(t),i.delete(n),a}function ce(t,n,r,e,u,i,o){switch(r){case"[object DataView]":if(t.byteLength!=n.byteLength||t.byteOffset!=n.byteOffset)break;t=t.buffer,n=n.buffer;case"[object ArrayBuffer]":
|
||||
if(t.byteLength!=n.byteLength||!e(new si(t),new si(n)))break;return true;case"[object Boolean]":case"[object Date]":case"[object Number]":return iu(+t,+n);case"[object Error]":return t.name==n.name&&t.message==n.message;case"[object RegExp]":case"[object String]":return t==n+"";case"[object Map]":var f=L;case"[object Set]":if(f||(f=M),t.size!=n.size&&!(2&i))break;return(r=o.get(t))?r==n:(i|=1,o.set(t,n),n=fe(f(t),f(n),e,u,i,o),o.delete(t),n);case"[object Symbol]":if(Hi)return Hi.call(t)==Hi.call(n)}
|
||||
return false}function ae(t){return _o(Se(t,F,Te),t+"")}function le(t){for(var n=t.name+"",r=Zi[n],e=ei.call(Zi,n)?r.length:0;e--;){var u=r[e],i=u.func;if(null==i||i==t)return u.name}return n}function se(t){return(ei.call(Ot,"placeholder")?Ot:t).placeholder}function he(){var t=Ot.iteratee||Mu,t=t===Mu?Kn:t;return arguments.length?t(arguments[0],arguments[1]):t}function pe(t,n){var r=t.__data__,e=typeof n;return("string"==e||"number"==e||"symbol"==e||"boolean"==e?"__proto__"!==n:null===n)?r[typeof n=="string"?"string":"hash"]:r.map;
|
||||
}function _e(t){for(var n=Su(t),r=n.length;r--;){var e=n[r],u=t[e];n[r]=[e,u,u===u&&!pu(u)]}return n}function ve(t,n){var r=null==t?F:t[n];return Pn(r)?r:F}function ge(t,n,r){n=me(n,t)?[n]:Sr(n);for(var e=-1,u=n.length,i=false;++e<u;){var o=Be(n[e]);if(!(i=null!=t&&r(t,o)))break;t=t[o]}return i||++e!=u?i:(u=t?t.length:0,!!u&&hu(u)&&je(o,u)&&(tf(t)||ou(t)))}function de(t){var n=t.length,r=t.constructor(n);return n&&"string"==typeof t[0]&&ei.call(t,"index")&&(r.index=t.index,r.input=t.input),r}function ye(t){
|
||||
return typeof t.constructor!="function"||ke(t)?{}:Xi(_i(t))}function be(r,e,u,i){var o=r.constructor;switch(e){case"[object ArrayBuffer]":return zr(r);case"[object Boolean]":case"[object Date]":return new o(+r);case"[object DataView]":return e=i?zr(r.buffer):r.buffer,new r.constructor(e,r.byteOffset,r.byteLength);case"[object Float32Array]":case"[object Float64Array]":case"[object Int8Array]":case"[object Int16Array]":case"[object Int32Array]":case"[object Uint8Array]":case"[object Uint8ClampedArray]":
|
||||
case"[object Uint16Array]":case"[object Uint32Array]":return e=i?zr(r.buffer):r.buffer,new r.constructor(e,r.byteOffset,r.length);case"[object Map]":return e=i?u(L(r),true):L(r),h(e,t,new r.constructor);case"[object Number]":case"[object String]":return new o(r);case"[object RegExp]":return e=new r.constructor(r.source,gt.exec(r)),e.lastIndex=r.lastIndex,e;case"[object Set]":return e=i?u(M(r),true):M(r),h(e,n,new r.constructor);case"[object Symbol]":return Hi?Gu(Hi.call(r)):{}}}function xe(t){return tf(t)||ou(t)||!!(bi&&t&&t[bi]);
|
||||
}function je(t,n){return n=null==n?9007199254740991:n,!!n&&(typeof t=="number"||jt.test(t))&&-1<t&&0==t%1&&t<n}function we(t,n,r){if(!pu(r))return false;var e=typeof n;return!!("number"==e?fu(r)&&je(n,r.length):"string"==e&&n in r)&&iu(r[n],t)}function me(t,n){if(tf(t))return false;var r=typeof t;return!("number"!=r&&"symbol"!=r&&"boolean"!=r&&null!=t&&!yu(t))||(rt.test(t)||!nt.test(t)||null!=n&&t in Gu(n))}function Ae(t){var n=le(t),r=Ot[n];return typeof r=="function"&&n in Dt.prototype&&(t===r||(n=co(r),
|
||||
!!n&&t===n[0]))}function ke(t){var n=t&&t.constructor;return t===(typeof n=="function"&&n.prototype||Xu)}function Ee(t,n){return function(r){return null!=r&&(r[t]===n&&(n!==F||t in Gu(r)))}}function Oe(t,n,r,e,u,i){return pu(t)&&pu(n)&&(i.set(n,t),tr(t,n,F,Oe,i),i.delete(n)),t}function Se(t,n,e){return n=Ri(n===F?t.length-1:n,0),function(){for(var u=arguments,i=-1,o=Ri(u.length-n,0),f=Pu(o);++i<o;)f[i]=u[n+i];for(i=-1,o=Pu(n+1);++i<n;)o[i]=u[i];return o[n]=e(f),r(t,this,o)}}function Ie(t,n){return 1==n.length?t:Rn(t,vr(n,0,-1));
|
||||
}function Re(t,n,r){var e=n+"";n=_o;var u,i=Ue;return u=(u=e.match(st))?u[1].split(ht):[],r=i(u,r),(i=r.length)&&(u=i-1,r[u]=(1<i?"& ":"")+r[u],r=r.join(2<i?", ":" "),e=e.replace(lt,"{\n/* [wrapped with "+r+"] */\n")),n(t,e)}function ze(t){var n=0,r=0;return function(){var e=Wi(),u=16-(e-r);if(r=e,0<u){if(500<=++n)return arguments[0]}else n=0;return t.apply(F,arguments)}}function We(t,n){var r=-1,e=t.length,u=e-1;for(n=n===F?e:dn(n,0,e);++r<n;){var e=cr(r,u),i=t[e];t[e]=t[r],t[r]=i}return t.length=n,
|
||||
t}function Be(t){if(typeof t=="string"||yu(t))return t;var n=t+"";return"0"==n&&1/t==-N?"-0":n}function Le(t){if(null!=t){try{return ri.call(t)}catch(t){}return t+""}return""}function Ue(t,n){return u(Z,function(r){var e="_."+r[0];n&r[1]&&!c(t,e)&&t.push(e)}),t.sort()}function Ce(t){if(t instanceof Dt)return t.clone();var n=new Mt(t.__wrapped__,t.__chain__);return n.__actions__=Ur(t.__actions__),n.__index__=t.__index__,n.__values__=t.__values__,n}function Me(t,n,r){var e=t?t.length:0;return e?(r=null==r?0:ju(r),
|
||||
0>r&&(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&&0==t%1&&9007199254740991>=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-1<hn(this.__data__,t)},Nt.prototype.set=function(t,n){var r=this.__data__,e=hn(r,t);return 0>e?(++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?t[n-1]:F,n=typeof n=="function"?(t.pop(),n):F;return qe(t,n)}),Lo=ae(function(t){function n(n){return gn(n,t)}var r=t.length,e=r?t[0]:0,u=this.__wrapped__;return!(1<r||this.__actions__.length)&&u instanceof Dt&&je(e)?(u=u.slice(e,+e+(r?1:0)),u.__actions__.push({func:Ke,args:[n],thisArg:F}),new Mt(u,this.__chain__).thru(function(t){return r&&!t.length&&t.push(F),t})):this.thru(n)}),Uo=Dr(function(t,n,r){ei.call(t,r)?++t[r]:vn(t,r,1)}),Co=Kr(Me),Mo=Kr(De),Do=Dr(function(t,n,r){
|
||||
ei.call(t,r)?t[r].push(n):vn(t,r,[n])}),To=lr(function(t,n,e){var u=-1,i=typeof n=="function",o=me(n),f=fu(t)?Pu(t.length):[];return to(t,function(t){var c=i?n:o&&null!=t?t[n]:F;f[++u]=c?r(c,t,e):Mn(t,n,e)}),f}),$o=Dr(function(t,n,r){vn(t,r,n)}),Fo=Dr(function(t,n,r){t[r?0:1].push(n)},function(){return[[],[]]}),No=lr(function(t,n){if(null==t)return[];var r=n.length;return 1<r&&we(t,n[0],n[1])?n=[]:2<r&&we(n[0],n[1],n[2])&&(n=[n[0]]),rr(t,En(n,1),[])}),Po=ji||function(){return Pt.Date.now()},Zo=lr(function(t,n,r){
|
||||
var e=1;if(r.length)var u=C(r,se(Zo)),e=32|e;return oe(t,e,n,r,u)}),qo=lr(function(t,n,r){var e=3;if(r.length)var u=C(r,se(qo)),e=32|e;return oe(n,e,t,r,u)}),Vo=lr(function(t,n){return jn(t,1,n)}),Ko=lr(function(t,n,r){return jn(t,mu(n)||0,r)});eu.Cache=Zt;var Go=lr(function(t,n){n=1==n.length&&tf(n[0])?l(n[0],S(he())):l(En(n,1),S(he()));var e=n.length;return lr(function(u){for(var i=-1,o=zi(u.length,e);++i<o;)u[i]=n[i].call(this,u[i]);return r(t,this,u)})}),Jo=lr(function(t,n){return oe(t,32,F,n,C(n,se(Jo)));
|
||||
}),Yo=lr(function(t,n){return oe(t,64,F,n,C(n,se(Yo)))}),Ho=ae(function(t,n){return oe(t,256,F,F,F,n)}),Qo=re(Wn),Xo=re(function(t,n){return t>=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));e<r;)i[u++]=vr(t,e,e+=n);return i},Ot.compact=function(t){for(var n=-1,r=t?t.length:0,e=0,u=[];++n<r;){var i=t[n];i&&(u[e++]=i)}return u},Ot.concat=function(){
|
||||
var t=arguments.length;if(!t)return[];for(var n=Pu(t-1),r=arguments[0];t--;)n[t-1]=arguments[t];return s(tf(r)?Ur(r):[r],En(n,1))},Ot.cond=function(t){var n=t?t.length:0,e=he();return t=n?l(t,function(t){if("function"!=typeof t[1])throw new Hu("Expected a function");return[e(t[0]),t[1]]}):[],lr(function(e){for(var u=-1;++u<n;){var i=t[u];if(r(i[0],this,e))return r(i[1],this,e)}})},Ot.conforms=function(t){return bn(yn(t,true))},Ot.constant=Uu,Ot.countBy=Uo,Ot.create=function(t,n){var r=Xi(t);return n?_n(r,n):r;
|
||||
},Ot.curry=tu,Ot.curryRight=nu,Ot.debounce=ru,Ot.defaults=gf,Ot.defaultsDeep=df,Ot.defer=Vo,Ot.delay=Ko,Ot.difference=go,Ot.differenceBy=yo,Ot.differenceWith=bo,Ot.drop=function(t,n,r){var e=t?t.length:0;return e?(n=r||n===F?1:ju(n),vr(t,0>n?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<e;)t[r++]=n;return t},Ot.filter=function(t,n){return(tf(t)?f:kn)(t,he(n,3))},Ot.flatMap=function(t,n){return En(He(t,n),1)},Ot.flatMapDeep=function(t,n){return En(He(t,n),N)},Ot.flatMapDepth=function(t,n,r){return r=r===F?1:ju(r),En(He(t,n),r)},Ot.flatten=Te,Ot.flattenDeep=function(t){return t&&t.length?En(t,N):[]},Ot.flattenDepth=function(t,n){
|
||||
return t&&t.length?(n=n===F?1:ju(n),En(t,n)):[]},Ot.flip=function(t){return oe(t,512)},Ot.flow=Mf,Ot.flowRight=Df,Ot.fromPairs=function(t){for(var n=-1,r=t?t.length:0,e={};++n<r;){var u=t[n];e[u[0]]=u[1]}return e},Ot.functions=function(t){return null==t?[]:In(t,Su(t))},Ot.functionsIn=function(t){return null==t?[]:In(t,Iu(t))},Ot.groupBy=Do,Ot.initial=function(t){return t&&t.length?vr(t,0,-1):[]},Ot.intersection=xo,Ot.intersectionBy=jo,Ot.intersectionWith=wo,Ot.invert=yf,Ot.invertBy=bf,Ot.invokeMap=To,
|
||||
Ot.iteratee=Mu,Ot.keyBy=$o,Ot.keys=Su,Ot.keysIn=Iu,Ot.map=He,Ot.mapKeys=function(t,n){var r={};return n=he(n,3),On(t,function(t,e,u){vn(r,n(t,e,u),t)}),r},Ot.mapValues=function(t,n){var r={};return n=he(n,3),On(t,function(t,e,u){vn(r,e,n(t,e,u))}),r},Ot.matches=function(t){return Qn(yn(t,true))},Ot.matchesProperty=function(t,n){return Xn(t,yn(n,true))},Ot.memoize=eu,Ot.merge=jf,Ot.mergeWith=wf,Ot.method=Tf,Ot.methodOf=$f,Ot.mixin=Du,Ot.negate=uu,Ot.nthArg=function(t){return t=ju(t),lr(function(n){return nr(n,t);
|
||||
})},Ot.omit=mf,Ot.omitBy=function(t,n){return Ru(t,uu(he(n)))},Ot.once=function(t){return Xe(2,t)},Ot.orderBy=function(t,n,r,e){return null==t?[]:(tf(n)||(n=null==n?[]:[n]),r=e?F:r,tf(r)||(r=null==r?[]:[r]),rr(t,n,r))},Ot.over=Ff,Ot.overArgs=Go,Ot.overEvery=Nf,Ot.overSome=Pf,Ot.partial=Jo,Ot.partialRight=Yo,Ot.partition=Fo,Ot.pick=Af,Ot.pickBy=Ru,Ot.property=$u,Ot.propertyOf=function(t){return function(n){return null==t?F:Rn(t,n)}},Ot.pull=mo,Ot.pullAll=Ne,Ot.pullAllBy=function(t,n,r){return t&&t.length&&n&&n.length?or(t,n,he(r,2)):t;
|
||||
},Ot.pullAllWith=function(t,n,r){return t&&t.length&&n&&n.length?or(t,n,F,r):t},Ot.pullAt=Ao,Ot.range=Zf,Ot.rangeRight=qf,Ot.rearg=Ho,Ot.reject=function(t,n){return(tf(t)?f:kn)(t,uu(he(n,3)))},Ot.remove=function(t,n){var r=[];if(!t||!t.length)return r;var e=-1,u=[],i=t.length;for(n=he(n,3);++e<i;){var o=t[e];n(o,e,t)&&(r.push(o),u.push(e))}return fr(t,u),r},Ot.rest=function(t,n){if(typeof t!="function")throw new Hu("Expected a function");return n=n===F?n:ju(n),lr(t,n)},Ot.reverse=Pe,Ot.sampleSize=function(t,n,r){
|
||||
return n=(r?we(t,n,r):n===F)?1:ju(n),(tf(t)?fn:hr)(t,n)},Ot.set=function(t,n,r){return null==t?t:pr(t,n,r)},Ot.setWith=function(t,n,r,e){return e=typeof e=="function"?e:F,null==t?t:pr(t,n,r,e)},Ot.shuffle=function(t){return(tf(t)?cn:_r)(t)},Ot.slice=function(t,n,r){var e=t?t.length:0;return e?(r&&typeof r!="number"&&we(t,n,r)?(n=0,r=e):(n=null==n?0:ju(n),r=r===F?e:ju(r)),vr(t,n,r)):[]},Ot.sortBy=No,Ot.sortedUniq=function(t){return t&&t.length?br(t):[]},Ot.sortedUniqBy=function(t,n){return t&&t.length?br(t,he(n,2)):[];
|
||||
},Ot.split=function(t,n,r){return r&&typeof r!="number"&&we(t,n,r)&&(n=r=F),r=r===F?4294967295: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&&-1<t.indexOf(n,r):!!e&&-1<d(t,n,r)},Ot.indexOf=function(t,n,r){var e=t?t.length:0;return e?(r=null==r?0:ju(r),
|
||||
0>r&&(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<Ri(n,r)},Ot.invoke=xf,Ot.isArguments=ou,Ot.isArray=tf,Ot.isArrayBuffer=nf,Ot.isArrayLike=fu,Ot.isArrayLikeObject=cu,Ot.isBoolean=function(t){return true===t||false===t||_u(t)&&"[object Boolean]"==oi.call(t)},Ot.isBuffer=rf,Ot.isDate=ef,Ot.isElement=function(t){return null!=t&&1===t.nodeType&&_u(t)&&!gu(t)},Ot.isEmpty=function(t){if(fu(t)&&(tf(t)||typeof t=="string"||typeof t.splice=="function"||rf(t)||ou(t)))return!t.length;
|
||||
var n=Et(t);if("[object Map]"==n||"[object Set]"==n)return!t.size;if(ke(t))return!Ii(t).length;for(var r in t)if(ei.call(t,r))return false;return true},Ot.isEqual=function(t,n){return $n(t,n)},Ot.isEqualWith=function(t,n,r){var e=(r=typeof r=="function"?r:F)?r(t,n):F;return e===F?$n(t,n,r):!!e},Ot.isError=au,Ot.isFinite=function(t){return typeof t=="number"&&Oi(t)},Ot.isFunction=lu,Ot.isInteger=su,Ot.isLength=hu,Ot.isMap=uf,Ot.isMatch=function(t,n){return t===n||Nn(t,n,_e(n))},Ot.isMatchWith=function(t,n,r){
|
||||
return r=typeof r=="function"?r:F,Nn(t,n,_e(n),r)},Ot.isNaN=function(t){return vu(t)&&t!=+t},Ot.isNative=function(t){if(so(t))throw new qu("Unsupported core-js use. Try https://github.com/es-shims.");return Pn(t)},Ot.isNil=function(t){return null==t},Ot.isNull=function(t){return null===t},Ot.isNumber=vu,Ot.isObject=pu,Ot.isObjectLike=_u,Ot.isPlainObject=gu,Ot.isRegExp=of,Ot.isSafeInteger=function(t){return su(t)&&-9007199254740991<=t&&9007199254740991>=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&&e<n?t+Xr(n-e,r):t},Ot.padStart=function(t,n,r){t=ku(t);var e=(n=ju(n))?T(t):0;return n&&e<n?Xr(n-e,r)+t:t},Ot.parseInt=function(t,n,r){return r||null==n?n=0:n&&(n=+n),Bi(ku(t).replace(ct,""),n||0)},Ot.random=function(t,n,r){if(r&&typeof r!="boolean"&&we(t,n,r)&&(n=r=F),
|
||||
r===F&&(typeof n=="boolean"?(r=n,n=F):typeof t=="boolean"&&(r=t,t=F)),t===F&&n===F?(t=0,n=1):(t=xu(t),n===F?(n=t,t=0):n=xu(n)),t>n){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);++e<u;){var i=null==t?F:t[Be(n[e])];i===F&&(e=u,i=r),t=lu(i)?i.call(t):i}return t},Ot.round=Hf,Ot.runInContext=w,Ot.sample=function(t){return(tf(t)?on:sr)(t)},Ot.size=function(t){if(null==t)return 0;if(fu(t))return du(t)?T(t):t.length;var n=Et(t);return"[object Map]"==n||"[object Set]"==n?t.size:Gn(t).length},Ot.snakeCase=zf,Ot.some=function(t,n,r){
|
||||
var e=tf(t)?_:gr;return r&&we(t,n,r)&&(n=F),e(t,he(n,3))},Ot.sortedIndex=function(t,n){return dr(t,n)},Ot.sortedIndexBy=function(t,n,r){return yr(t,n,he(r,2))},Ot.sortedIndexOf=function(t,n){var r=t?t.length:0;if(r){var e=dr(t,n);if(e<r&&iu(t[e],n))return e}return-1},Ot.sortedLastIndex=function(t,n){return dr(t,n,true)},Ot.sortedLastIndexBy=function(t,n,r){return yr(t,n,he(r,2),true)},Ot.sortedLastIndexOf=function(t,n){if(t&&t.length){var r=dr(t,n,true)-1;if(iu(t[r],n))return r}return-1},Ot.startCase=Wf,
|
||||
Ot.startsWith=function(t,n,r){return t=ku(t),r=dn(ju(r),0,t.length),n=jr(n),t.slice(r,r+n.length)==n},Ot.subtract=Qf,Ot.sum=function(t){return t&&t.length?k(t,Cu):0},Ot.sumBy=function(t,n){return t&&t.length?k(t,he(n,2)):0},Ot.template=function(t,n,r){var e=Ot.templateSettings;r&&we(t,n,r)&&(n=F),t=ku(t),n=pf({},n,e,an),r=pf({},n.imports,e.imports,an);var u,i,o=Su(r),f=I(r,o),c=0;r=n.interpolate||mt;var a="__p+='";r=Ju((n.escape||mt).source+"|"+r.source+"|"+(r===tt?vt:mt).source+"|"+(n.evaluate||mt).source+"|$","g");
|
||||
var l="sourceURL"in n?"//# sourceURL="+n.sourceURL+"\n":"";if(t.replace(r,function(n,r,e,o,f,l){return e||(e=o),a+=t.slice(c,l).replace(At,B),r&&(u=true,a+="'+__e("+r+")+'"),f&&(i=true,a+="';"+f+";\n__p+='"),e&&(a+="'+((__t=("+e+"))==null?'':__t)+'"),c=l+n.length,n}),a+="';",(n=n.variable)||(a="with(obj){"+a+"}"),a=(i?a.replace(q,""):a).replace(V,"$1").replace(K,"$1;"),a="function("+(n||"obj")+"){"+(n?"":"obj||(obj={});")+"var __t,__p=''"+(u?",__e=_.escape":"")+(i?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":";")+a+"return __p}",
|
||||
n=Uf(function(){return Vu(o,l+"return "+a).apply(F,f)}),n.source=a,au(n))throw n;return n},Ot.times=function(t,n){if(t=ju(t),1>t||9007199254740991<t)return[];var r=4294967295,e=zi(t,4294967295);for(n=he(n),t-=4294967295,e=E(e,n);++r<t;)n(r);return e},Ot.toFinite=xu,Ot.toInteger=ju,Ot.toLength=wu,Ot.toLower=function(t){return ku(t).toLowerCase()},Ot.toNumber=mu,Ot.toSafeInteger=function(t){return dn(ju(t),-9007199254740991,9007199254740991)},Ot.toString=ku,Ot.toUpper=function(t){return ku(t).toUpperCase();
|
||||
},Ot.trim=function(t,n,r){return(t=ku(t))&&(r||n===F)?t.replace(ft,""):t&&(n=jr(n))?(t=$(t),r=$(n),n=z(t,r),r=W(t,r)+1,Ir(t,n,r).join("")):t},Ot.trimEnd=function(t,n,r){return(t=ku(t))&&(r||n===F)?t.replace(at,""):t&&(n=jr(n))?(t=$(t),n=W(t,$(n))+1,Ir(t,0,n).join("")):t},Ot.trimStart=function(t,n,r){return(t=ku(t))&&(r||n===F)?t.replace(ct,""):t&&(n=jr(n))?(t=$(t),n=z(t,$(n)),Ir(t,n).join("")):t},Ot.truncate=function(t,n){var r=30,e="...";if(pu(n))var u="separator"in n?n.separator:u,r="length"in n?ju(n.length):r,e="omission"in n?jr(n.omission):e;
|
||||
t=ku(t);var i=t.length;if(Wt.test(t))var o=$(t),i=o.length;if(r>=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),-1<u&&(r=r.slice(0,u)));return r+e},Ot.unescape=function(t){return(t=ku(t))&&Y.test(t)?t.replace(G,en):t},Ot.uniqueId=function(t){
|
||||
var n=++ui;return ku(t)+n},Ot.upperCase=Bf,Ot.upperFirst=Lf,Ot.each=Je,Ot.eachRight=Ye,Ot.first=$e,Du(Ot,function(){var t={};return On(Ot,function(n,r){ei.call(Ot.prototype,r)||(t[r]=n)}),t}(),{chain:false}),Ot.VERSION="4.16.2",u("bind bindKey curry curryRight partial partialRight".split(" "),function(t){Ot[t].placeholder=Ot}),u(["drop","take"],function(t,n){Dt.prototype[t]=function(r){var e=this.__filtered__;if(e&&!n)return new Dt(this);r=r===F?1:Ri(ju(r),0);var u=this.clone();return e?u.__takeCount__=zi(r,u.__takeCount__):u.__views__.push({
|
||||
size:zi(r,4294967295),type:t+(0>u.__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__&&(0<t||0>n)?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;++c<a;){var l=o[c],s=l.size;switch(l.type){case"drop":f+=s;break;case"dropRight":t-=s;break;case"take":t=zi(t,f+s);break;case"takeRight":f=Ri(f,t-s)}}if(t={start:f,end:t},o=t.start,f=t.end,t=f-o,
|
||||
u=u?f:o-1,o=this.__iteratees__,f=o.length,c=0,a=zi(t,this.__takeCount__),!e||200>i||i==t&&a==t)return Ar(n,this.__actions__);e=[];t:for(;t--&&c<a;){for(u+=r,i=-1,l=n[u];++i<f;){var h=o[i],s=h.type,h=(0,h.iteratee)(l);if(2==s)l=h;else if(!h){if(1==s)continue t;break t}}e[c++]=l}return e},Ot.prototype.at=Lo,Ot.prototype.chain=function(){return Ve(this)},Ot.prototype.commit=function(){return new Mt(this.value(),this.__chain__)},Ot.prototype.next=function(){this.__values__===F&&(this.__values__=bu(this.value()));
|
||||
var t=this.__index__>=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);
|
|
@ -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',
|
||||
|
|
|
@ -63,4 +63,6 @@ return [
|
|||
'2fa' => '2FA',
|
||||
'logout' => 'Logout',
|
||||
'admin_cp' => 'Admin Control Panel',
|
||||
'optional' => 'Optional',
|
||||
'read_only' => 'Read Only',
|
||||
];
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
{{-- Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com> --}}
|
||||
|
||||
{{-- 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')
|
||||
<h1>Administrative Overview<small>A quick glance at your system.</small></h1>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{{ route('admin.index') }}">Admin</a></li>
|
||||
<li class="active">Index</li>
|
||||
</ol>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="box
|
||||
@if(Version::isLatestPanel())
|
||||
box-success
|
||||
@else
|
||||
box-danger
|
||||
@endif
|
||||
">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">System Information</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
@if (Version::isLatestPanel())
|
||||
You are running Pterodactyl Panel version <code>{{ Version::getCurrentPanel() }}</code>. Your panel is up-to-date!
|
||||
@else
|
||||
Your panel is <strong>not up-to-date!</strong> The latest version is <a href="https://github.com/Pterodactyl/Panel/releases/v{{ Version::getPanel() }}" target="_blank"><code>{{ Version::getPanel() }}</code></a> and you are currently running version <code>{{ Version::getCurrentPanel() }}</code>.
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-6 col-sm-3 text-center">
|
||||
<a href="{{ Version::getDiscord() }}"><button class="btn btn-warning" style="width:100%;"><i class="fa fa-fw fa-support"></i> Get Help <small>(via Discord)</small></button></a>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-3 text-center">
|
||||
<a href="https://docs.pterodactyl.io"><button class="btn btn-primary" style="width:100%;"><i class="fa fa-fw fa-link"></i> Documentation</button></a>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-3 text-center">
|
||||
<a href="https://github.com/Pterodactyl/Panel"><button class="btn btn-primary" style="width:100%;"><i class="fa fa-fw fa-support"></i> Github</button></a>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-3 text-center">
|
||||
<a href="https://patreon.com/pterry"><button class="btn btn-success" style="width:100%;"><i class="fa fa-fw fa-money"></i> Support the Project</button></a>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
|
@ -0,0 +1,118 @@
|
|||
{{-- Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com> --}}
|
||||
|
||||
{{-- 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')
|
||||
<h1>Nodes<small>All nodes available on the system.</small></h1>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{{ route('admin.index') }}">Admin</a></li>
|
||||
<li class="active">Nodes</li>
|
||||
</ol>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="box box-primary">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Node List</h3>
|
||||
<div class="box-tools">
|
||||
<form action="{{ route('admin.nodes') }}" method="GET">
|
||||
<div class="input-group input-group-sm" style="width: 300px;">
|
||||
<input type="text" name="query" class="form-control pull-right" value="{{ request()->input('query') }}" placeholder="Search Nodes">
|
||||
<div class="input-group-btn">
|
||||
<button type="submit" class="btn btn-default"><i class="fa fa-search"></i></button>
|
||||
<a href="{{ route('admin.nodes.new') }}"><button type="button" class="btn btn-sm btn-primary" style="border-radius: 0 3px 3px 0;margin-left:-1px;">Create New</button></a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-body table-responsive no-padding">
|
||||
<table class="table table-hover">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Name</th>
|
||||
<th>Location</th>
|
||||
<th>Memory</th>
|
||||
<th>Disk</th>
|
||||
<th class="text-center">Servers</th>
|
||||
<th class="text-center">SSL</th>
|
||||
<th class="text-center">Public</th>
|
||||
</tr>
|
||||
@foreach ($nodes as $node)
|
||||
<tr>
|
||||
<td class="text-center text-muted left-icon" data-action="ping" data-secret="{{ $node->daemonSecret }}" data-location="{{ $node->scheme }}://{{ $node->fqdn }}:{{ $node->daemonListen }}"><i class="fa fa-fw fa-refresh fa-spin"></i></td>
|
||||
<td><a href="{{ route('admin.nodes.view', $node->id) }}">{{ $node->name }}</td>
|
||||
<td>{{ $node->location->short }}</td>
|
||||
<td>{{ $node->memory }} MB</td>
|
||||
<td>{{ $node->disk }} MB</td>
|
||||
<td class="text-center">{{ $node->servers_count }}</td>
|
||||
<td class="text-center" style="color:{{ ($node->scheme === 'https') ? '#50af51' : '#d9534f' }}"><i class="fa fa-{{ ($node->scheme === 'https') ? 'lock' : 'unlock' }}"></i></td>
|
||||
<td class="text-center"><i class="fa fa-{{ ($node->public) ? 'eye' : 'eye-slash' }}"></i></td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="box-footer with-border">
|
||||
<div class="col-md-12 text-center">{!! $nodes->render() !!}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('footer-scripts')
|
||||
@parent
|
||||
<script>
|
||||
(function pingNodes() {
|
||||
$('td[data-action="ping"]').each(function(i, element) {
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: $(element).data('location'),
|
||||
headers: {
|
||||
'X-Access-Token': $(element).data('secret'),
|
||||
},
|
||||
timeout: 5000
|
||||
}).done(function (data) {
|
||||
$(element).find('i').tooltip({
|
||||
title: 'v' + data.version,
|
||||
});
|
||||
$(element).removeClass('text-muted').find('i').removeClass().addClass('fa fa-fw fa-heartbeat faa-pulse animated').css('color', '#50af51');
|
||||
}).fail(function () {
|
||||
$(element).removeClass('text-muted').find('i').removeClass().addClass('fa fa-fw fa-heart-o').css('color', '#d9534f');
|
||||
});
|
||||
}).promise().done(function () {
|
||||
setTimeout(pingNodes, 10000);
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@endsection
|
|
@ -0,0 +1,244 @@
|
|||
{{-- Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com> --}}
|
||||
|
||||
{{-- 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')
|
||||
<h1>{{ $node->name }}<small>Control allocations available for servers on this node.</small></h1>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{{ route('admin.index') }}">Admin</a></li>
|
||||
<li><a href="{{ route('admin.nodes') }}">Nodes</a></li>
|
||||
<li><a href="{{ route('admin.nodes.view', $node->id) }}">{{ $node->name }}</a></li>
|
||||
<li class="active">Allocations</li>
|
||||
</ol>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="nav-tabs-custom nav-tabs-floating">
|
||||
<ul class="nav nav-tabs">
|
||||
<li><a href="{{ route('admin.nodes.view', $node->id) }}">About</a></li>
|
||||
<li><a href="{{ route('admin.nodes.view.settings', $node->id) }}">Settings</a></li>
|
||||
<li><a href="{{ route('admin.nodes.view.configuration', $node->id) }}">Configuration</a></li>
|
||||
<li class="active"><a href="{{ route('admin.nodes.view.allocation', $node->id) }}">Allocation</a></li>
|
||||
<li><a href="{{ route('admin.nodes.view.servers', $node->id) }}">Servers</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-8">
|
||||
<div class="box box-primary">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Existing Allocations</h3>
|
||||
</div>
|
||||
<div class="box-body table-responsive no-padding">
|
||||
<table class="table table-hover" style="margin-bottom:0;">
|
||||
<tr>
|
||||
<th>IP Address <i class="fa fa-fw fa-minus-square" style="font-weight:normal;color:#d9534f;cursor:pointer;" data-toggle="modal" data-target="#allocationModal"></i></th>
|
||||
<th>IP Alias</th>
|
||||
<th>Port</th>
|
||||
<th>Assigned To</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
@foreach($node->allocations as $allocation)
|
||||
<tr>
|
||||
<td class="col-sm-3 middle">{{ $allocation->ip }}</td>
|
||||
<td class="col-sm-3 middle">
|
||||
<input class="form-control input-sm" type="text" value="{{ $allocation->ip_alias }}" data-action="set-alias" data-id="{{ $allocation->id }}" placeholder="none" />
|
||||
<span class="input-loader"><i class="fa fa-refresh fa-spin fa-fw"></i></span>
|
||||
</td>
|
||||
<td class="col-sm-2 middle">{{ $allocation->port }}</td>
|
||||
<td class="col-sm-3 middle">
|
||||
@if(! is_null($allocation->server))
|
||||
<a href="{{ route('admin.servers.view', $allocation->server_id) }}">{{ $allocation->server->name }}</a>
|
||||
@endif
|
||||
</td>
|
||||
<td class="col-sm-1 middle">
|
||||
@if(is_null($allocation->server_id))
|
||||
<button data-action="deallocate" data-id="{{ $allocation->id }}" class="btn btn-sm btn-danger"><i class="fa fa-trash-o"></i></button>
|
||||
@else
|
||||
<button class="btn btn-sm disabled"><i class="fa fa-trash-o"></i></button>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</table>
|
||||
</div>
|
||||
<div class="box-footer text-center">
|
||||
{{ $node->allocations->render() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<form action="{{ route('admin.nodes.view.allocation', $node->id) }}" method="POST">
|
||||
<div class="box box-success">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Assign New Allocations</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="form-group">
|
||||
<label for="pAllocationIP" class="control-label">IP Address</label>
|
||||
<div>
|
||||
<select class="form-control" name="allocation_ip" id="pAllocationIP" multiple>
|
||||
@foreach($node->allocations->unique('ip')->values()->all() as $allocation)
|
||||
<option value="{{ $allocation->ip }}">{{ $allocation->ip }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<p class="text-muted small">Enter an IP address to assign ports to here.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="pAllocationIP" class="control-label">IP Alias</label>
|
||||
<div>
|
||||
<input type="text" id="pAllocationAlias" class="form-control" name="allocation_alias" placeholder="alias" />
|
||||
<p class="text-muted small">If you would like to assign a default alias to these allocations enter it here.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="pAllocationPorts" class="control-label">Ports</label>
|
||||
<div>
|
||||
<select class="form-control" name="allocation_ports[]" id="pAllocationPorts" multiple></select>
|
||||
<p class="text-muted small">Enter individual ports or port ranges here separated by commas or spaces.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
{!! csrf_field() !!}
|
||||
<button type="submit" class="btn btn-success btn-sm pull-right">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" id="allocationModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title">Delete Allocations for IP Block</h4>
|
||||
</div>
|
||||
<form action="{{ route('admin.nodes.view.allocation.removeBlock', $node->id) }}" method="POST">
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<select class="form-control" name="ip">
|
||||
@foreach($node->allocations->unique('ip')->values()->all() as $allocation)
|
||||
<option value="{{ $allocation->ip }}">{{ $allocation->ip }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
{{{ csrf_field() }}}
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
<button type="submit" class="btn btn-danger">Delete Allocations</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('footer-scripts')
|
||||
@parent
|
||||
<script>
|
||||
$('#pAllocationIP').select2({
|
||||
tags: true,
|
||||
maximumSelectionLength: 1,
|
||||
tokenSeparators: [',', ' '],
|
||||
});
|
||||
$('#pAllocationPorts').select2({
|
||||
tags: true,
|
||||
tokenSeparators: [',', ' '],
|
||||
});
|
||||
$('button[data-action="deallocate"]').click(function (event) {
|
||||
event.preventDefault();
|
||||
var element = $(this);
|
||||
var allocation = $(this).data('id');
|
||||
swal({
|
||||
title: '',
|
||||
text: 'Are you sure you want to delete this allocation?',
|
||||
type: 'warning',
|
||||
showCancelButton: true,
|
||||
allowOutsideClick: true,
|
||||
closeOnConfirm: false,
|
||||
confirmButtonText: 'Delete',
|
||||
confirmButtonColor: '#d9534f',
|
||||
showLoaderOnConfirm: true
|
||||
}, function () {
|
||||
$.ajax({
|
||||
method: 'DELETE',
|
||||
url: Router.route('admin.nodes.view.allocation.removeSingle', { id: Pterodactyl.node.id, allocation: allocation }),
|
||||
headers: { 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content') },
|
||||
}).done(function (data) {
|
||||
element.parent().parent().addClass('warning').delay(100).fadeOut();
|
||||
swal({ type: 'success', title: 'Port Deleted!' });
|
||||
}).fail(function (jqXHR) {
|
||||
console.error(jqXHR);
|
||||
swal({
|
||||
title: 'Whoops!',
|
||||
text: jqXHR.responseJSON.error,
|
||||
type: 'error'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
var typingTimer;
|
||||
$('input[data-action="set-alias"]').keyup(function () {
|
||||
clearTimeout(typingTimer);
|
||||
$(this).parent().removeClass('has-error has-success');
|
||||
typingTimer = setTimeout(sendAlias, 250, $(this));
|
||||
});
|
||||
|
||||
var fadeTimers = [];
|
||||
function sendAlias(element) {
|
||||
element.parent().find('.input-loader').show();
|
||||
clearTimeout(fadeTimers[element.data('id')]);
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
url: Router.route('admin.nodes.view.allocation.setAlias', { id: Pterodactyl.node.id }),
|
||||
headers: { 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content') },
|
||||
data: {
|
||||
alias: element.val(),
|
||||
allocation_id: element.data('id'),
|
||||
}
|
||||
}).done(function (data) {
|
||||
element.parent().addClass('has-success');
|
||||
}).fail(function (jqXHR) {
|
||||
console.error(jqXHR);
|
||||
element.parent().addClass('has-error');
|
||||
}).always(function () {
|
||||
element.parent().find('.input-loader').hide();
|
||||
fadeTimers[element.data('id')] = setTimeout(clearHighlight, 2500, element);
|
||||
});
|
||||
}
|
||||
|
||||
function clearHighlight(element) {
|
||||
element.parent().removeClass('has-error has-success');
|
||||
}
|
||||
</script>
|
||||
@endsection
|
|
@ -0,0 +1,101 @@
|
|||
{{-- Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com> --}}
|
||||
|
||||
{{-- 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')
|
||||
<h1>{{ $node->name }}<small>Your daemon configuration file.</small></h1>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{{ route('admin.index') }}">Admin</a></li>
|
||||
<li><a href="{{ route('admin.nodes') }}">Nodes</a></li>
|
||||
<li><a href="{{ route('admin.nodes.view', $node->id) }}">{{ $node->name }}</a></li>
|
||||
<li class="active">Configuration</li>
|
||||
</ol>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="nav-tabs-custom nav-tabs-floating">
|
||||
<ul class="nav nav-tabs">
|
||||
<li><a href="{{ route('admin.nodes.view', $node->id) }}">About</a></li>
|
||||
<li><a href="{{ route('admin.nodes.view.settings', $node->id) }}">Settings</a></li>
|
||||
<li class="active"><a href="{{ route('admin.nodes.view.configuration', $node->id) }}">Configuration</a></li>
|
||||
<li><a href="{{ route('admin.nodes.view.allocation', $node->id) }}">Allocation</a></li>
|
||||
<li><a href="{{ route('admin.nodes.view.servers', $node->id) }}">Servers</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-8">
|
||||
<div class="box box-primary">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Configuration File</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<pre class="no-margin">{{ $node->getConfigurationAsJson(true) }}</pre>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<p class="no-margin">This file should be placed in your daemon's <code>config</code> directory in a file called <code>core.json</code>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="box box-success">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Auto-Deploy</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<p class="text-muted small">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. <em>Tokens are only valid for 5 minutes.</em></p>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<button type="button" id="configTokenBtn" class="btn btn-sm btn-default" style="width:100%;">Generate Token</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('footer-scripts')
|
||||
@parent
|
||||
<script>
|
||||
$('#configTokenBtn').on('click', function (event) {
|
||||
$.getJSON('{{ route('admin.nodes.view.configuration.token', $node->id) }}').done(function (data) {
|
||||
swal({
|
||||
type: 'success',
|
||||
title: 'Token created.',
|
||||
text: 'Your token will expire <strong>in 5 minutes.</strong><br /><br />' +
|
||||
'<p>To auto-configure your node run the following command:<br /><small><pre>npm run configure -- --panel-url {{ config('app.url') }} --token ' + data.token + '</pre></small></p>',
|
||||
html: true
|
||||
})
|
||||
}).fail(function () {
|
||||
swal({
|
||||
title: 'Error',
|
||||
text: 'Something went wrong creating your token.',
|
||||
type: 'error'
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
|
@ -0,0 +1,163 @@
|
|||
{{-- Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com> --}}
|
||||
|
||||
{{-- 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')
|
||||
<h1>{{ $node->name }}<small>A quick overview of your node.</small></h1>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{{ route('admin.index') }}">Admin</a></li>
|
||||
<li><a href="{{ route('admin.nodes') }}">Nodes</a></li>
|
||||
<li class="active">{{ $node->name }}</li>
|
||||
</ol>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="nav-tabs-custom nav-tabs-floating">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active"><a href="{{ route('admin.nodes.view', $node->id) }}">About</a></li>
|
||||
<li><a href="{{ route('admin.nodes.view.settings', $node->id) }}">Settings</a></li>
|
||||
<li><a href="{{ route('admin.nodes.view.configuration', $node->id) }}">Configuration</a></li>
|
||||
<li><a href="{{ route('admin.nodes.view.allocation', $node->id) }}">Allocation</a></li>
|
||||
<li><a href="{{ route('admin.nodes.view.servers', $node->id) }}">Servers</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-8">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="box box-primary">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Information</h3>
|
||||
</div>
|
||||
<div class="box-body table-responsive no-padding">
|
||||
<table class="table table-hover">
|
||||
<tr>
|
||||
<td>Daemon Version</td>
|
||||
<td><code data-attr="info-version"><i class="fa fa-refresh fa-fw fa-spin"></i></code> (Latest: <code>{{ Version::getDaemon() }}</code>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>System Information</td>
|
||||
<td data-attr="info-system"><i class="fa fa-refresh fa-fw fa-spin"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total CPU Cores</td>
|
||||
<td data-attr="info-cpus"><i class="fa fa-refresh fa-fw fa-spin"></i></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12">
|
||||
<div class="box box-danger">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Delete Node</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<p class="no-margin">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.</p>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<form action="{{ route('admin.nodes.view.delete', $node->id) }}" method="POST">
|
||||
{!! csrf_field() !!}
|
||||
{!! method_field('DELETE') !!}
|
||||
<button type="submit" class="btn btn-danger btn-sm pull-right" {{ ($node->servers_count < 1) ?: 'disabled' }}>Yes, Delete This Node</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="box box-primary">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">At-a-Glance</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="info-box bg-{{ $stats['disk']['css'] }}">
|
||||
<span class="info-box-icon"><i class="ion ion-ios-folder-outline"></i></span>
|
||||
<div class="info-box-content" style="padding: 15px 10px 0;">
|
||||
<span class="info-box-text">Disk Space Allocated</span>
|
||||
<span class="info-box-number">{{ $stats['disk']['value'] }} Mb</span>
|
||||
<div class="progress">
|
||||
<div class="progress-bar" style="width: {{ $stats['disk']['percent'] }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<div class="info-box bg-{{ $stats['memory']['css'] }}">
|
||||
<span class="info-box-icon"><i class="ion ion-ios-barcode-outline"></i></span>
|
||||
<div class="info-box-content" style="padding: 15px 10px 0;">
|
||||
<span class="info-box-text">Memory Allocated</span>
|
||||
<span class="info-box-number">{{ $stats['memory']['value'] }} Mb</span>
|
||||
<div class="progress">
|
||||
<div class="progress-bar" style="width: {{ $stats['memory']['percent'] }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<div class="info-box bg-gray">
|
||||
<span class="info-box-icon"><i class="ion ion-social-buffer-outline"></i></span>
|
||||
<div class="info-box-content" style="padding: 23px 10px 0;">
|
||||
<span class="info-box-text">Total Servers</span>
|
||||
<span class="info-box-number">{{ $node->servers_count }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('footer-scripts')
|
||||
@parent
|
||||
<script>
|
||||
(function getInformation() {
|
||||
$.ajax({
|
||||
method: 'GET',
|
||||
url: '{{ $node->scheme }}://{{ $node->fqdn }}:{{ $node->daemonListen }}',
|
||||
timeout: 5000,
|
||||
headers: {
|
||||
'X-Access-Token': '{{ $node->daemonSecret }}'
|
||||
},
|
||||
}).done(function (data) {
|
||||
$('[data-attr="info-version"]').html(data.version);
|
||||
$('[data-attr="info-system"]').html(data.system.type + '(' + data.system.arch + ') <code>' + data.system.release + '</code>');
|
||||
$('[data-attr="info-cpus"]').html(data.system.cpus);
|
||||
}).fail(function (jqXHR) {
|
||||
|
||||
}).always(function() {
|
||||
setTimeout(getInformation, 10000);
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@endsection
|
|
@ -0,0 +1,90 @@
|
|||
{{-- Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com> --}}
|
||||
|
||||
{{-- 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')
|
||||
<h1>{{ $node->name }}<small>All servers currently assigned to this node.</small></h1>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{{ route('admin.index') }}">Admin</a></li>
|
||||
<li><a href="{{ route('admin.nodes') }}">Nodes</a></li>
|
||||
<li><a href="{{ route('admin.nodes.view', $node->id) }}">{{ $node->name }}</a></li>
|
||||
<li class="active">Servers</li>
|
||||
</ol>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="nav-tabs-custom nav-tabs-floating">
|
||||
<ul class="nav nav-tabs">
|
||||
<li><a href="{{ route('admin.nodes.view', $node->id) }}">About</a></li>
|
||||
<li><a href="{{ route('admin.nodes.view.settings', $node->id) }}">Settings</a></li>
|
||||
<li><a href="{{ route('admin.nodes.view.configuration', $node->id) }}">Configuration</a></li>
|
||||
<li><a href="{{ route('admin.nodes.view.allocation', $node->id) }}">Allocation</a></li>
|
||||
<li class="active"><a href="{{ route('admin.nodes.view.servers', $node->id) }}">Servers</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="box box-primary">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Process Manager</h3>
|
||||
</div>
|
||||
<div class="box-body table-responsive no-padding">
|
||||
<table class="table table-hover">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Server Name</th>
|
||||
<th>Owner</th>
|
||||
<th>Service</th>
|
||||
<th class="text-center">Memory</th>
|
||||
<th class="text-center">Disk</th>
|
||||
<th class="text-center">CPU</th>
|
||||
<th class="text-center">Status</th>
|
||||
</tr>
|
||||
@foreach($node->servers as $server)
|
||||
<tr data-server="{{ $server->uuid }}">
|
||||
<td><code>{{ $server->uuidShort }}</code></td>
|
||||
<td><a href="{{ route('admin.servers.view', $server->id) }}">{{ $server->name }}</a></td>
|
||||
<td><a href="{{ route('admin.users.view', $server->owner_id) }}">{{ $server->user->username }}</a></td>
|
||||
<td>{{ $server->service->name }} ({{ $server->option->name }})</td>
|
||||
<td class="text-center"><span data-action="memory">NaN</span> / {{ $server->memory === 0 ? '∞' : $server->memory }} MB</td>
|
||||
<td class="text-center">{{ $server->disk }} MB</td>
|
||||
<td class="text-center"><span data-action="cpu" data-cpumax="{{ $server->cpu }}">NaN</span> %</td>
|
||||
<td class="text-center" data-action="status">NaN</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('footer-scripts')
|
||||
@parent
|
||||
{!! Theme::js('js/admin/node/view-servers.js') !!}
|
||||
@endsection
|
|
@ -0,0 +1,223 @@
|
|||
{{-- Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com> --}}
|
||||
|
||||
{{-- 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')
|
||||
<h1>{{ $node->name }}<small>Configure your node settings.</small></h1>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{{ route('admin.index') }}">Admin</a></li>
|
||||
<li><a href="{{ route('admin.nodes') }}">Nodes</a></li>
|
||||
<li><a href="{{ route('admin.nodes.view', $node->id) }}">{{ $node->name }}</a></li>
|
||||
<li class="active">Settings</li>
|
||||
</ol>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="nav-tabs-custom nav-tabs-floating">
|
||||
<ul class="nav nav-tabs">
|
||||
<li><a href="{{ route('admin.nodes.view', $node->id) }}">About</a></li>
|
||||
<li class="active"><a href="{{ route('admin.nodes.view.settings', $node->id) }}">Settings</a></li>
|
||||
<li><a href="{{ route('admin.nodes.view.configuration', $node->id) }}">Configuration</a></li>
|
||||
<li><a href="{{ route('admin.nodes.view.allocation', $node->id) }}">Allocation</a></li>
|
||||
<li><a href="{{ route('admin.nodes.view.servers', $node->id) }}">Servers</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form action="{{ route('admin.nodes.view.settings', $node->id) }}" method="POST">
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Settings</h3>
|
||||
</div>
|
||||
<div class="box-body row">
|
||||
<div class="form-group col-xs-12">
|
||||
<label for="name" class="control-label">Node Name</label>
|
||||
<div>
|
||||
<input type="text" autocomplete="off" name="name" class="form-control" value="{{ old('name', $node->name) }}" />
|
||||
<p class="text-muted"><small>Character limits: <code>a-zA-Z0-9_.-</code> and <code>[Space]</code> (min 1, max 100 characters).</small></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-xs-12">
|
||||
<label for="name" class="control-label">Location</label>
|
||||
<div>
|
||||
<select name="location_id" class="form-control">
|
||||
@foreach($locations as $location)
|
||||
<option value="{{ $location->id }}" {{ (old('location_id', $node->location_id) === $location->id) ? 'selected' : '' }}>{{ $location->long }} ({{ $location->short }})</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-xs-12">
|
||||
<label for="public" class="control-label">Allow Automatic Allocation <sup><a data-toggle="tooltip" data-placement="top" title="Allow automatic allocation to this Node?">?</a></sup></label>
|
||||
<div>
|
||||
<input type="radio" name="public" value="1" {{ (old('public', $node->public) === '1') ? 'checked' : '' }} id="public_1" checked> <label for="public_1" style="padding-left:5px;">Yes</label><br />
|
||||
<input type="radio" name="public" value="0" {{ (old('public', $node->public) === '0') ? 'checked' : '' }} id="public_0"> <label for="public_0" style="padding-left:5px;">No</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-xs-12">
|
||||
<label for="fqdn" class="control-label">Fully Qualified Domain Name</label>
|
||||
<div>
|
||||
<input type="text" autocomplete="off" name="fqdn" class="form-control" value="{{ old('fqdn', $node->fqdn) }}" />
|
||||
</div>
|
||||
<p class="text-muted"><small>Please enter domain name (e.g <code>node.example.com</code>) to be used for connecting to the daemon. An IP address may only be used if you are not using SSL for this node.
|
||||
<a tabindex="0" data-toggle="popover" data-trigger="focus" title="Why do I need a FQDN?" data-content="In order to secure communications between your server and this node we use SSL. We cannot generate a SSL certificate for IP Addresses, and as such you will need to provide a FQDN.">Why?</a>
|
||||
</small></p>
|
||||
</div>
|
||||
<div class="form-group col-xs-12">
|
||||
<label for="scheme" class="control-label"><span class="label label-warning"><i class="fa fa-power-off"></i></span> Secure Socket Layer</label>
|
||||
<div class="row" style="padding: 7px 0;">
|
||||
<div class="col-xs-6">
|
||||
<input type="radio" name="scheme" value="https" id="scheme_ssl" {{ (old('scheme', $node->scheme) === 'https') ? 'checked' : '' }}/> <label for="scheme_ssl" style="padding-left: 5px;">Enable HTTPS/SSL</label>
|
||||
</div>
|
||||
<div class="col-xs-6">
|
||||
<input type="radio" name="scheme" value="http" id="scheme_nossl" {{ (old('scheme', $node->scheme) === 'http') ? 'checked' : '' }}/> <label for="scheme_nossl" style="padding-left: 5px;">Disable HTTPS/SSL</label>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-muted"><small>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.</small></p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Allocation Limits</h3>
|
||||
</div>
|
||||
<div class="box-body row">
|
||||
<div class="col-xs-12">
|
||||
<div class="row">
|
||||
<div class="form-group col-xs-6">
|
||||
<label for="memory" class="control-label">Total Memory</label>
|
||||
<div class="input-group">
|
||||
<input type="text" name="memory" class="form-control" data-multiplicator="true" value="{{ old('memory', $node->memory) }}"/>
|
||||
<span class="input-group-addon">MB</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-xs-6">
|
||||
<label for="memory_overallocate" class="control-label">Overallocate</label>
|
||||
<div class="input-group">
|
||||
<input type="text" name="memory_overallocate" class="form-control" value="{{ old('memory_overallocate', $node->memory_overallocate) }}"/>
|
||||
<span class="input-group-addon">%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-muted small">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.</p>
|
||||
</div>
|
||||
<div class="col-xs-12">
|
||||
<div class="row">
|
||||
<div class="form-group col-xs-6">
|
||||
<label for="disk" class="control-label">Disk Space</label>
|
||||
<div class="input-group">
|
||||
<input type="text" name="disk" class="form-control" data-multiplicator="true" value="{{ old('disk', $node->disk) }}"/>
|
||||
<span class="input-group-addon">MB</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-xs-6">
|
||||
<label for="disk_overallocate" class="control-label">Overallocate</label>
|
||||
<div class="input-group">
|
||||
<input type="text" name="disk_overallocate" class="form-control" value="{{ old('disk_overallocate', $node->disk_overallocate) }}"/>
|
||||
<span class="input-group-addon">%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-muted small">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.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">General Configuration</h3>
|
||||
</div>
|
||||
<div class="box-body row">
|
||||
<div class="form-group col-xs-12">
|
||||
<label for="disk_overallocate" class="control-label">Maximum Web Upload Filesize</label>
|
||||
<div class="input-group">
|
||||
<input type="text" name="upload_size" class="form-control" value="{{ old('upload_size', $node->upload_size) }}"/>
|
||||
<span class="input-group-addon">MB</span>
|
||||
</div>
|
||||
<p class="text-muted"><small>Enter the maximum size of files that can be uploaded through the web-based file manager.</small></p>
|
||||
</div>
|
||||
<div class="col-xs-12">
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
<label for="daemonListen" class="control-label"><span class="label label-warning"><i class="fa fa-power-off"></i></span> Daemon Port</label>
|
||||
<div>
|
||||
<input type="text" name="daemonListen" class="form-control" value="{{ old('daemonListen', $node->daemonListen) }}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<label for="daemonSFTP" class="control-label"><span class="label label-warning"><i class="fa fa-power-off"></i></span> Daemon SFTP Port</label>
|
||||
<div>
|
||||
<input type="text" name="daemonSFTP" class="form-control" value="{{ old('daemonSFTP', $node->daemonSFTP) }}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p class="text-muted"><small>The daemon runs its own SFTP management container and does not use the SSHd process on the main physical server. <Strong>Do not use the same port that you have assigned for your physcial server's SSH process.</strong></small></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12">
|
||||
<div class="box box-primary">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Save Settings</h3>
|
||||
</div>
|
||||
<div class="box-body row">
|
||||
<div class="form-group col-sm-6">
|
||||
<div>
|
||||
<input type="checkbox" name="reset_secret" id="reset_secret" /> <label for="reset_secret" class="control-label">Reset Daemon Master Key</label>
|
||||
</div>
|
||||
<p class="text-muted"><small>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.</small></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
{!! csrf_field() !!}
|
||||
<button type="submit" class="btn btn-primary pull-right">Save Changes</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@endsection
|
||||
|
||||
@section('footer-scripts')
|
||||
@parent
|
||||
<script>
|
||||
$('[data-toggle="popover"]').popover({
|
||||
placement: 'auto'
|
||||
});
|
||||
$('select[name="location_id"]').select2();
|
||||
</script>
|
||||
@endsection
|
|
@ -0,0 +1,94 @@
|
|||
{{-- Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com> --}}
|
||||
|
||||
{{-- 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')
|
||||
<h1>Servers<small>All servers available on the system.</small></h1>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{{ route('admin.index') }}">Admin</a></li>
|
||||
<li class="active">Servers</li>
|
||||
</ol>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="box box-primary">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Server List</h3>
|
||||
<div class="box-tools">
|
||||
<form action="{{ route('admin.servers') }}" method="GET">
|
||||
<div class="input-group input-group-sm" style="width: 300px;">
|
||||
<input type="text" name="query" class="form-control pull-right" value="{{ request()->input('query') }}" placeholder="Search Servers">
|
||||
<div class="input-group-btn">
|
||||
<button type="submit" class="btn btn-default"><i class="fa fa-search"></i></button>
|
||||
<a href="{{ route('admin.servers.new') }}"><button type="button" class="btn btn-sm btn-primary" style="border-radius: 0 3px 3px 0;margin-left:-1px;">Create New</button></a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-body table-responsive no-padding">
|
||||
<table class="table table-hover">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Server Name</th>
|
||||
<th>Owner</th>
|
||||
<th>Node</th>
|
||||
<th>Connection</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
@foreach ($servers as $server)
|
||||
<tr data-server="{{ $server->uuidShort }}">
|
||||
<td><code>{{ $server->uuidShort }}</code></td>
|
||||
<td><a href="{{ route('admin.servers.view', $server->id) }}">{{ $server->name }}</a></td>
|
||||
<td><a href="{{ route('admin.users.view', $server->user->id) }}">{{ $server->user->username }}</a></td>
|
||||
<td><a href="{{ route('admin.nodes.view', $server->node->id) }}">{{ $server->node->name }}</a></td>
|
||||
<td>
|
||||
<code>{{ $server->allocation->alias }}:{{ $server->allocation->port }}</code>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
@if($server->suspended && ! $server->trashed())
|
||||
<span class="label bg-maroon">Suspended</span>
|
||||
@elseif($server->trashed())
|
||||
<span class="label label-danger">Pending Deletion</span>
|
||||
@elseif(! $server->installed)
|
||||
<span class="label label-warning">Installing</span>
|
||||
@else
|
||||
<span class="label label-success">Active</span>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="box-footer with-border">
|
||||
<div class="col-md-12 text-center">{!! $servers->render() !!}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
|
@ -0,0 +1,257 @@
|
|||
{{-- Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com> --}}
|
||||
|
||||
{{-- 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')
|
||||
<h1>Create Server<small>Add a new server to the panel.</small></h1>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{{ route('admin.index') }}">Admin</a></li>
|
||||
<li><a href="{{ route('admin.servers') }}">Servers</a></li>
|
||||
<li class="active">Create Server</li>
|
||||
</ol>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<form action="{{ route('admin.servers.new') }}" method="POST">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Core Details</h3>
|
||||
</div>
|
||||
<div class="box-body row">
|
||||
<div class="form-group col-sm-6">
|
||||
<label for="pName">Server Name</label>
|
||||
<input type="text" class="form-control" id="pName" name="name" value="{{ old('name') }}" placeholder="Server Name">
|
||||
<p class="small text-muted no-margin">Character limits: <code>a-z A-Z 0-9 _ - .</code> and <code>[Space]</code> (max 200 characters).</p>
|
||||
</div>
|
||||
<div class="form-group col-sm-6">
|
||||
<label for="pUserId">Server Owner</label>
|
||||
<select class="form-control" style="padding-left:0;" name="user_id" id="pUserId"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="box">
|
||||
<div class="overlay" id="allocationLoader" style="display:none;"><i class="fa fa-refresh fa-spin"></i></div>
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Allocation Management</h3>
|
||||
</div>
|
||||
<div class="box-body row">
|
||||
<div class="form-group col-sm-4">
|
||||
<label for="pLocationId">Location</label>
|
||||
<select name="location_id" id="pLocationId" class="form-control">
|
||||
@foreach($locations as $location)
|
||||
<option value="{{ $location->id }}"
|
||||
@if($location->id === old('location_id'))
|
||||
selected="selected"
|
||||
@endif
|
||||
>{{ $location->long }} ({{ $location->short }})</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<p class="small text-muted no-margin">The location in which this server will be deployed.</p>
|
||||
</div>
|
||||
<div class="form-group col-sm-4">
|
||||
<label for="pNodeId">Node</label>
|
||||
<select name="node_id" id="pNodeId" class="form-control"></select>
|
||||
<p class="small text-muted no-margin">The node which this server will be deployed to.</p>
|
||||
</div>
|
||||
<div class="form-group col-sm-4">
|
||||
<label for="pAllocation">Default Allocation</label>
|
||||
<select name="allocation_id" id="pAllocation" class="form-control"></select>
|
||||
<p class="small text-muted no-margin">The main allocation that will be assigned to this server.</p>
|
||||
</div>
|
||||
<div class="form-group col-sm-12">
|
||||
<label for="pAllocationAdditional">Additional Allocation(s)</label>
|
||||
<select name="allocation_additional[]" id="pAllocationAdditional" class="form-control" multiple></select>
|
||||
<p class="small text-muted no-margin">Additional allocations to assign to this server on creation.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<p class="text-muted small no-margin">
|
||||
<input type="checkbox" name="auto_deploy" id="pAutoDeploy" @if(old('auto_deploy'))checked="checked"@endif/>
|
||||
<label for="pAutoDeploy">Check this box if you want the panel to automatically select a node and allocation for this server in the given location.</label>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Resource Management</h3>
|
||||
</div>
|
||||
<div class="box-body row">
|
||||
<div class="form-group col-sm-4">
|
||||
<label for="pMemory">Memory</label>
|
||||
<div class="input-group">
|
||||
<input type="text" value="{{ old('memory') }}" class="form-control" name="memory" id="pMemory" />
|
||||
<span class="input-group-addon">MB</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-sm-4">
|
||||
<label for="pSwap">Swap</label>
|
||||
<div class="input-group">
|
||||
<input type="text" value="{{ old('swap') }}" class="form-control" name="swap" id="pSwap" />
|
||||
<span class="input-group-addon">MB</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-sm-4">
|
||||
<label for="pOOMDisabled">Out-of-Memory Killer</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon">
|
||||
<input type="checkbox" id="pOOMDisabled" name="oom_disabled"/>
|
||||
</span>
|
||||
<input type="text" class="form-control" readonly style="background:transparent !important;" value="Disable OOM Killer" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer no-border" style="padding: 0 10px 10px;">
|
||||
<div class="callout callout-info callout-slim no-margin">
|
||||
<p class="small no-margin">If you do not want to assign swap space to a server simply put <code>0</code> for the value, or <code>-1</code> to allow unlimited swap space. If you want to disable memory limiting on a server simply enter <code>0</code> 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.<p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-body row">
|
||||
<div class="form-group col-sm-4">
|
||||
<label for="pDisk">Disk Space</label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" value="{{ old('disk') }}" name="disk" id="pDisk" />
|
||||
<span class="input-group-addon">MB</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-sm-4">
|
||||
<label for="pCPU">CPU Limit</label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" value="{{ old('cpu') }}" name="cpu" id="pCPU" />
|
||||
<span class="input-group-addon">%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-sm-4">
|
||||
<label for="pIO">Block IO Weight</label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" value="{{ old('io') }}" value="500" name="io" id="pIO" />
|
||||
<span class="input-group-addon">I/O</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer no-border" style="padding: 0 10px 10px;">
|
||||
<div class="callout callout-info callout-slim">
|
||||
<p class="small no-margin">If you do not want to limit CPU usage set the value to <code>0</code>. To determine a value, take the number <em>physical</em> cores and multiply it by 100. For example, on a quad core system <code>(4 * 100 = 400)</code> there is <code>400%</code> available. To limit a server to using half of a single core, you would set the value to <code>50</code>. To allow a server to use up to two physical cores, set the value to <code>200</code>. BlockIO should be a value between <code>10</code> and <code>1000</code>. Please see <a href="https://docs.docker.com/engine/reference/run/#/block-io-bandwidth-blkio-constraint" target="_blank">this documentation</a> for more information about it.<p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Service Configuration</h3>
|
||||
</div>
|
||||
<div class="box-body row">
|
||||
<div class="form-group col-xs-12">
|
||||
<label for="pServiceId">Service</label>
|
||||
<select name="service_id" id="pServiceId" class="form-control">
|
||||
@foreach($services as $service)
|
||||
<option value="{{ $service->id }}"
|
||||
@if($service->id === old('service_id'))
|
||||
selected="selected"
|
||||
@endif
|
||||
>{{ $service->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<p class="small text-muted no-margin">Select the type of service that this server will be running.</p>
|
||||
</div>
|
||||
<div class="form-group col-xs-12">
|
||||
<label for="pOptionId">Option</label>
|
||||
<select name="option_id" id="pOptionId" class="form-control"></select>
|
||||
<p class="small text-muted no-margin">Select the type of sub-service that this server will be running.</p>
|
||||
</div>
|
||||
<div class="form-group col-xs-12">
|
||||
<label for="pPackId">Service Pack</label>
|
||||
<select name="pack_id" id="pPackId" class="form-control"></select>
|
||||
<p class="small text-muted no-margin">Select a service pack to be automatically installed on this server when first created.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Docker Configuration</h3>
|
||||
</div>
|
||||
<div class="box-body row">
|
||||
<div class="form-group col-xs-12">
|
||||
<label for="pDefaultContainer">Default Container</label>
|
||||
<input type="text" id="pDefaultContainer" readonly class="form-control" />
|
||||
<p class="small text-muted no-margin">This is the default Docker container that will be used to run this server.</p>
|
||||
</div>
|
||||
<div class="form-group col-xs-12">
|
||||
<label for="pCustomContainer">Custom Container</label>
|
||||
<input type="text" name="custom_container" value="{{ old('custom_container') }}" id="pCustomContainer" class="form-control" />
|
||||
<p class="small text-muted no-margin">If you would like to use a custom Docker container please enter it here, otherwise leave empty.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Startup Configuration</h3>
|
||||
</div>
|
||||
<div class="box-body row">
|
||||
<div class="form-group col-xs-12">
|
||||
<label for="pStartup">Startup Command</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon bg-gray" id="pStartupExecutable"></span>
|
||||
<input type="text" id="pStartup" value="{{ old('startup') }}" class="form-control" name="startup" />
|
||||
</div>
|
||||
<p class="small text-muted no-margin">The following data replacers are avaliable for the startup command: <code>@{{SERVER_MEMORY}}</code>, <code>@{{SERVER_IP}}</code>, and <code>@{{SERVER_PORT}}</code>. They will be replaced with the allocated memory, server ip, and server port respectively.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-header with-border" style="margin-top:-10px;">
|
||||
<h3 class="box-title">Service Variables</h3>
|
||||
</div>
|
||||
<div class="box-body row" id="appendVariablesTo"></div>
|
||||
<div class="box-footer">
|
||||
{!! csrf_field() !!}
|
||||
<input type="submit" class="btn btn-success pull-right" value="Create Server" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@endsection
|
||||
|
||||
@section('footer-scripts')
|
||||
@parent
|
||||
{!! Theme::js('vendor/lodash/lodash.js') !!}
|
||||
{!! Theme::js('js/admin/new-server.js') !!}
|
||||
@endsection
|
|
@ -0,0 +1,158 @@
|
|||
{{-- Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com> --}}
|
||||
|
||||
{{-- 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')
|
||||
<h1>{{ $server->name }}<small>Control allocations and system resources for this server.</small></h1>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{{ route('admin.index') }}">Admin</a></li>
|
||||
<li><a href="{{ route('admin.servers') }}">Servers</a></li>
|
||||
<li><a href="{{ route('admin.servers.view', $server->id) }}">{{ $server->name }}</a></li>
|
||||
<li class="active">Build Configuration</li>
|
||||
</ol>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="nav-tabs-custom nav-tabs-floating">
|
||||
<ul class="nav nav-tabs">
|
||||
<li><a href="{{ route('admin.servers.view', $server->id) }}">About</a></li>
|
||||
@if(! $server->trashed() && $server->installed === 1)
|
||||
<li><a href="{{ route('admin.servers.view.details', $server->id) }}">Details</a></li>
|
||||
<li class="active"><a href="{{ route('admin.servers.view.build', $server->id) }}">Build Configuration</a></li>
|
||||
<li><a href="{{ route('admin.servers.view.startup', $server->id) }}">Startup</a></li>
|
||||
<li><a href="{{ route('admin.servers.view.database', $server->id) }}">Database</a></li>
|
||||
@endif
|
||||
@if(! $server->trashed())
|
||||
<li><a href="{{ route('admin.servers.view.manage', $server->id) }}">Manage</a></li>
|
||||
@endif
|
||||
<li class="tab-danger"><a href="{{ route('admin.servers.view.delete', $server->id) }}">Delete</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<form action="{{ route('admin.servers.view.build', $server->id) }}" method="POST">
|
||||
<div class="col-sm-5">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">System Resources</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="form-group">
|
||||
<label for="memory" class="control-label">Allocated Memory</label>
|
||||
<div class="input-group">
|
||||
<input type="text" name="memory" data-multiplicator="true" class="form-control" value="{{ old('memory', $server->memory) }}"/>
|
||||
<span class="input-group-addon">MB</span>
|
||||
</div>
|
||||
<p class="text-muted small">The maximum amount of memory allowed for this container. Setting this to <code>0</code> will allow unlimited memorry in a container.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="swap" class="control-label">Allocated Swap</label>
|
||||
<div class="input-group">
|
||||
<input type="text" name="swap" data-multiplicator="true" class="form-control" value="{{ old('swap', $server->swap) }}"/>
|
||||
<span class="input-group-addon">MB</span>
|
||||
</div>
|
||||
<p class="text-muted small">Setting this to <code>0</code> will disable swap space on this server. Setting to <code>-1</code> will allow unlimited swap.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="cpu" class="control-label">CPU Limit</label>
|
||||
<div class="input-group">
|
||||
<input type="text" name="cpu" class="form-control" value="{{ old('cpu', $server->cpu) }}"/>
|
||||
<span class="input-group-addon">%</span>
|
||||
</div>
|
||||
<p class="text-muted small">Each <em>physical</em> core on the system is considered to be <code>100%</code>. Setting this value to <code>0</code> will allow a server to use CPU time without restrictions.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="io" class="control-label">Block IO Proportion</label>
|
||||
<div>
|
||||
<input type="text" name="io" class="form-control" value="{{ old('io', $server->io) }}"/>
|
||||
</div>
|
||||
<p class="text-muted small">Changing this value can have negative effects on all containers on the system. We strongly recommend leaving this value as <code>500</code>.</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-7">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Allocation Management</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="form-group">
|
||||
<label for="pAllocation" class="control-label">Game Port</label>
|
||||
<select id="pAllocation" name="allocation_id" class="form-control">
|
||||
@foreach ($assigned as $assignment)
|
||||
<option value="{{ $assignment->id }}"
|
||||
@if($assignment->id === $server->allocation_id)
|
||||
selected="selected"
|
||||
@endif
|
||||
>{{ $assignment->alias }}:{{ $assignment->port }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<p class="text-muted small">The default connection address that will be used for this game server.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="pAddAllocations" class="control-label">Assign Additional Ports</label>
|
||||
<div>
|
||||
<select name="add_allocations[]" class="form-control" multiple id="pAddAllocations">
|
||||
@foreach ($unassigned as $assignment)
|
||||
<option value="{{ $assignment->id }}">{{ $assignment->alias }}:{{ $assignment->port }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<p class="text-muted small">Please note that due to software limitations you cannot assign identical ports on different IPs to the same server.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="pRemoveAllocations" class="control-label">Remove Additional Ports</label>
|
||||
<div>
|
||||
<select name="remove_allocations[]" class="form-control" multiple id="pRemoveAllocations">
|
||||
@foreach ($assigned as $assignment)
|
||||
<option value="{{ $assignment->id }}" @if($server->allocation_id === $assignment->id)disabled @endif>{{ $assignment->alias }}:{{ $assignment->port }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<p class="text-muted small">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.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
{!! csrf_field() !!}
|
||||
<button type="submit" class="btn btn-primary pull-right">Update Build Configuration</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('footer-scripts')
|
||||
@parent
|
||||
<script>
|
||||
$('#pAddAllocations').select2();
|
||||
$('#pRemoveAllocations').select2();
|
||||
$('#pAllocation').select2();
|
||||
</script>
|
||||
@endsection
|
|
@ -0,0 +1,192 @@
|
|||
{{-- Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com> --}}
|
||||
|
||||
{{-- 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')
|
||||
<h1>{{ $server->name }}<small>Manage server databases.</small></h1>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{{ route('admin.index') }}">Admin</a></li>
|
||||
<li><a href="{{ route('admin.servers') }}">Servers</a></li>
|
||||
<li><a href="{{ route('admin.servers.view', $server->id) }}">{{ $server->name }}</a></li>
|
||||
<li class="active">Databases</li>
|
||||
</ol>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="nav-tabs-custom nav-tabs-floating">
|
||||
<ul class="nav nav-tabs">
|
||||
<li><a href="{{ route('admin.servers.view', $server->id) }}">About</a></li>
|
||||
@if(! $server->trashed() && $server->installed === 1)
|
||||
<li><a href="{{ route('admin.servers.view.details', $server->id) }}">Details</a></li>
|
||||
<li><a href="{{ route('admin.servers.view.build', $server->id) }}">Build Configuration</a></li>
|
||||
<li><a href="{{ route('admin.servers.view.startup', $server->id) }}">Startup</a></li>
|
||||
<li class="active"><a href="{{ route('admin.servers.view.database', $server->id) }}">Database</a></li>
|
||||
@endif
|
||||
@if(! $server->trashed())
|
||||
<li><a href="{{ route('admin.servers.view.manage', $server->id) }}">Manage</a></li>
|
||||
@endif
|
||||
<li class="tab-danger"><a href="{{ route('admin.servers.view.delete', $server->id) }}">Delete</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-7">
|
||||
<div class="box box-primary">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Active Databases</h3>
|
||||
</div>
|
||||
<div class="box-body table-responsible no-padding">
|
||||
<table class="table table-hover">
|
||||
<tr>
|
||||
<th>Database</th>
|
||||
<th>Username</th>
|
||||
<th>Connections From</th>
|
||||
<th>Host</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
@foreach($server->databases as $database)
|
||||
<tr>
|
||||
<td>{{ $database->database }}</td>
|
||||
<td>{{ $database->username }}</td>
|
||||
<td>{{ $database->remote }}</td>
|
||||
<td><code>{{ $database->host->host }}:{{ $database->host->port }}</code></td>
|
||||
<td class="text-center">
|
||||
<button data-action="reset-password" data-id="{{ $database->id }}" class="btn btn-xs btn-primary"><i class="fa fa-refresh"></i></button>
|
||||
<button data-action="remove" data-id="{{ $database->id }}" class="btn btn-xs btn-danger"><i class="fa fa-trash"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
<div class="box box-success">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Create New Database</h3>
|
||||
</div>
|
||||
<form action="{{ route('admin.servers.view.database', $server->id) }}" method="POST">
|
||||
<div class="box-body">
|
||||
<div class="form-group">
|
||||
<label for="pDatabaseHost" class="control-label">Database Host</label>
|
||||
<select id="pDatabaseHost" name="host" class="form-control">
|
||||
@foreach($hosts as $host)
|
||||
<option value="{{ $host->id }}">{{ $host->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<p class="text-muted small">Select the host database server that this database should be created on.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="pDatabaseName" class="control-label">Database</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon">s{{ $server->id }}_</span>
|
||||
<input id="pDatabaseName" type="text" name="database" class="form-control" placeholder="database" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="pConnections" class="control-label">Connections</label>
|
||||
<input id="pConnections" type="text" name="connection" class="form-control" placeholder="%" value="%" />
|
||||
<p class="text-muted small">This should reflect the IP address that connections are allowed from. Uses standard MySQL notation. If unsure leave as <code>%</code>.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
{!! csrf_field() !!}
|
||||
<p class="text-muted small no-margin">A username and password for this database will be randomly generated after form submission.</p>
|
||||
<input type="submit" class="btn btn-sm btn-success pull-right" value="Create Database" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('footer-scripts')
|
||||
@parent
|
||||
<script>
|
||||
$('#pDatabaseHost').select2();
|
||||
$('[data-action="remove"]').click(function (event) {
|
||||
event.preventDefault();
|
||||
var self = $(this);
|
||||
swal({
|
||||
title: '',
|
||||
type: 'warning',
|
||||
text: 'Are you sure that you want to delete this database? There is no going back, all data will immediately be removed.',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: 'Delete',
|
||||
confirmButtonColor: '#d9534f',
|
||||
closeOnConfirm: false,
|
||||
showLoaderOnConfirm: true,
|
||||
}, function () {
|
||||
$.ajax({
|
||||
method: 'DELETE',
|
||||
url: Router.route('admin.servers.view.database.delete', { id: '{{ $server->id }}', database: self.data('id') }),
|
||||
headers: { 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content') },
|
||||
}).done(function () {
|
||||
self.parent().parent().slideUp();
|
||||
swal.close();
|
||||
}).fail(function (jqXHR) {
|
||||
console.error(jqXHR);
|
||||
swal({
|
||||
type: 'error',
|
||||
title: 'Whoops!',
|
||||
text: (typeof jqXHR.responseJSON.error !== 'undefined') ? jqXHR.responseJSON.error : 'An error occured while processing this request.'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
$('[data-action="reset-password"]').click(function (e) {
|
||||
e.preventDefault();
|
||||
var block = $(this);
|
||||
$(this).addClass('disabled').find('i').addClass('fa-spin');
|
||||
$.ajax({
|
||||
type: 'PATCH',
|
||||
url: Router.route('admin.servers.view.database', { id: '{{ $server->id }}' }),
|
||||
headers: { 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content') },
|
||||
data: { database: $(this).data('id') },
|
||||
}).done(function (data) {
|
||||
swal({
|
||||
type: 'success',
|
||||
title: '',
|
||||
text: 'The password for this database has been reset.',
|
||||
});
|
||||
}).fail(function(jqXHR, textStatus, errorThrown) {
|
||||
console.error(jqXHR);
|
||||
var error = 'An error occured while trying to process this request.';
|
||||
if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {
|
||||
error = jqXHR.responseJSON.error;
|
||||
}
|
||||
swal({
|
||||
type: 'error',
|
||||
title: 'Whoops!',
|
||||
text: error
|
||||
});
|
||||
}).always(function () {
|
||||
block.removeClass('disabled').find('i').removeClass('fa-spin');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
|
@ -0,0 +1,141 @@
|
|||
{{-- Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com> --}}
|
||||
|
||||
{{-- 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')
|
||||
<h1>{{ $server->name }}<small>Delete this server from the panel.</small></h1>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{{ route('admin.index') }}">Admin</a></li>
|
||||
<li><a href="{{ route('admin.servers') }}">Servers</a></li>
|
||||
<li><a href="{{ route('admin.servers.view', $server->id) }}">{{ $server->name }}</a></li>
|
||||
<li class="active">Delete</li>
|
||||
</ol>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="nav-tabs-custom nav-tabs-floating">
|
||||
<ul class="nav nav-tabs">
|
||||
<li><a href="{{ route('admin.servers.view', $server->id) }}">About</a></li>
|
||||
@if(! $server->trashed() && $server->installed === 1)
|
||||
<li><a href="{{ route('admin.servers.view.details', $server->id) }}">Details</a></li>
|
||||
<li><a href="{{ route('admin.servers.view.build', $server->id) }}">Build Configuration</a></li>
|
||||
<li><a href="{{ route('admin.servers.view.startup', $server->id) }}">Startup</a></li>
|
||||
<li><a href="{{ route('admin.servers.view.database', $server->id) }}">Database</a></li>
|
||||
@endif
|
||||
@if(! $server->trashed())
|
||||
<li><a href="{{ route('admin.servers.view.manage', $server->id) }}">Manage</a></li>
|
||||
@endif
|
||||
<li class="tab-danger active"><a href="{{ route('admin.servers.view.delete', $server->id) }}">Delete</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
@if($server->trashed())
|
||||
<div class="col-xs-12">
|
||||
<div class="box box-danger">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Marked for Deletion</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<p>This server is currently marked for deletion by the system <strong>{{ Carbon::parse($server->deleted_at)->addMinutes(env('APP_DELETE_MINUTES', 10))->diffForHumans() }}</strong>.</p>
|
||||
<p class="text-danger small">Deleting a server is an irreversible action. <strong>All server data</strong> (including files and users) will be removed from the system.</p>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<form action="{{ route('admin.servers.view.delete.cancel', $server->id) }}" method="POST" style="display:inline;">
|
||||
{!! csrf_field() !!}
|
||||
<button type="submit" class="btn btn-default btn-sm">Cancel Deletion Request</button>
|
||||
</form>
|
||||
<form data-action="delete" action="{{ route('admin.servers.view.delete.continue', ['id' => $server->id, 'force' => 'force']) }}" method="POST" style="display:inline;">
|
||||
{!! csrf_field() !!}
|
||||
<button type="submit" class="btn btn-danger btn-sm pull-right"><strong>Forcibly</strong> Delete Now</button>
|
||||
</form>
|
||||
<form data-action="delete" action="{{ route('admin.servers.view.delete.continue', $server->id) }}" method="POST" style="display:inline">
|
||||
{!! csrf_field() !!}
|
||||
<button type="submit" class="btn btn-danger btn-sm pull-right" style="margin-right:10px;">Safely Delete Now</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
<div class="col-xs-6">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Safely Delete Server</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<p>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.</p>
|
||||
<p class="text-danger small">Deleting a server is an irreversible action. <strong>All server data</strong> (including files and users) will be removed from the system.</p>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<form action="{{ route('admin.servers.view.delete', $server->id) }}" method="POST">
|
||||
{!! csrf_field() !!}
|
||||
<input type="hidden" name="is_force" value="0" />
|
||||
<button type="submit" class="btn btn-danger">Safely Delete This Server</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-6">
|
||||
<div class="box box-danger">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Force Delete Server</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<p>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.</p>
|
||||
<p class="text-danger small">Deleting a server is an irreversible action. <strong>All server data</strong> (including files and users) will be removed from the system. This method may leave dangling files on your daemon if it reports an error.</p>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<form action="{{ route('admin.servers.view.delete', $server->id) }}" method="POST">
|
||||
{!! csrf_field() !!}
|
||||
<input type="hidden" name="is_force" value="1" />
|
||||
<button type="submit" class="btn btn-danger">Forcibly Delete This Server</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('footer-scripts')
|
||||
@parent
|
||||
<script>
|
||||
$('form[data-action="delete"]').submit(function (event) {
|
||||
event.preventDefault();
|
||||
swal({
|
||||
title: '',
|
||||
type: 'warning',
|
||||
text: 'Are you sure that you want to delete this server? There is no going back, all data will immediately be removed.',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: 'Delete',
|
||||
confirmButtonColor: '#d9534f',
|
||||
closeOnConfirm: false
|
||||
}, function () {
|
||||
event.target.submit();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
|
@ -0,0 +1,170 @@
|
|||
{{-- Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com> --}}
|
||||
|
||||
{{-- 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')
|
||||
<h1>{{ $server->name }}<small>Edit details for this server including owner and container.</small></h1>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{{ route('admin.index') }}">Admin</a></li>
|
||||
<li><a href="{{ route('admin.servers') }}">Servers</a></li>
|
||||
<li><a href="{{ route('admin.servers.view', $server->id) }}">{{ $server->name }}</a></li>
|
||||
<li class="active">Details</li>
|
||||
</ol>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="nav-tabs-custom nav-tabs-floating">
|
||||
<ul class="nav nav-tabs">
|
||||
<li><a href="{{ route('admin.servers.view', $server->id) }}">About</a></li>
|
||||
@if(! $server->trashed() && $server->installed === 1)
|
||||
<li class="active"><a href="{{ route('admin.servers.view.details', $server->id) }}">Details</a></li>
|
||||
<li><a href="{{ route('admin.servers.view.build', $server->id) }}">Build Configuration</a></li>
|
||||
<li><a href="{{ route('admin.servers.view.startup', $server->id) }}">Startup</a></li>
|
||||
<li><a href="{{ route('admin.servers.view.database', $server->id) }}">Database</a></li>
|
||||
@endif
|
||||
@if(! $server->trashed())
|
||||
<li><a href="{{ route('admin.servers.view.manage', $server->id) }}">Manage</a></li>
|
||||
@endif
|
||||
<li class="tab-danger"><a href="{{ route('admin.servers.view.delete', $server->id) }}">Delete</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="box box-primary">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Base Information</h3>
|
||||
</div>
|
||||
<form action="{{ route('admin.servers.view.details', $server->id) }}" method="POST">
|
||||
<div class="box-body">
|
||||
<div class="form-group">
|
||||
<label for="name" class="control-label">Server Name</label>
|
||||
<input type="text" name="name" value="{{ old('name', $server->name) }}" class="form-control" />
|
||||
<p class="text-muted small">Character limits: <code>a-zA-Z0-9_-</code> and <code>[Space]</code> (max 35 characters).</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="pUserId" class="control-label">Server Owner</label>
|
||||
<select name="owner_id" class="form-control" id="pUserId">
|
||||
<option value="{{ $server->owner_id }}" selected>{{ $server->user->email }}</option>
|
||||
</select>
|
||||
<p class="text-muted small">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.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="name" class="control-label">Daemon Secret Token</label>
|
||||
<input type="text" disabled value="{{ $server->daemonSecret }}" class="form-control" />
|
||||
<p class="text-muted small">This token should not be shared with anyone as it has full control over this server.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="checkbox" name="reset_token" id="pResetToken"/> <label for="pResetToken">Reset Daemon Token</label>
|
||||
<p class="text-muted small">Resetting this token will cause any requests using the old token to fail.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
{!! csrf_field() !!}
|
||||
<input type="submit" class="btn btn-sm btn-primary" value="Update Details" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="box box-success">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Container Setup</h3>
|
||||
</div>
|
||||
<form action="{{ route('admin.servers.view.details.container', $server->id) }}" method="POST">
|
||||
<div class="box-body">
|
||||
<div class="form-group">
|
||||
<label for="name" class="control-label">Docker Container Image</label>
|
||||
<input type="text" name="docker_image" value="{{ $server->image }}" class="form-control" />
|
||||
<p class="text-muted small">The docker image to use for this server. The default image for this service and option combination is <code>{{ $server->option->docker_image }}</code>.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
{!! csrf_field() !!}
|
||||
<input type="submit" class="btn btn-sm btn-primary" value="Update Docker Container" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('footer-scripts')
|
||||
@parent
|
||||
<script>
|
||||
$('#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 '<div class="user-block"> \
|
||||
<img class="img-circle img-bordered-xs" src="https://www.gravatar.com/avatar/' + data.md5 + '?s=120" alt="User Image"> \
|
||||
<span class="username"> \
|
||||
<a href="#">' + data.name_first + ' ' + data.name_last +'</a> \
|
||||
</span> \
|
||||
<span class="description"><strong>' + data.email + '</strong> - ' + data.username + '</span> \
|
||||
</div>';
|
||||
},
|
||||
templateSelection: function (data) {
|
||||
if (typeof data.name_first === 'undefined') {
|
||||
data = {
|
||||
md5: '{{ md5(strtolower($server->user->email)) }}',
|
||||
name_first: '{{ $server->user->name_first }}',
|
||||
name_last: '{{ $server->user->name_last }}',
|
||||
email: '{{ $server->user->email }}',
|
||||
id: {{ $server->owner_id }}
|
||||
};
|
||||
}
|
||||
|
||||
return '<div> \
|
||||
<span> \
|
||||
<img class="img-rounded img-bordered-xs" src="https://www.gravatar.com/avatar/' + data.md5 + '?s=120" style="height:28px;margin-top:-4px;" alt="User Image"> \
|
||||
</span> \
|
||||
<span style="padding-left:5px;"> \
|
||||
' + data.name_first + ' ' + data.name_last + ' (<strong>' + data.email + '</strong>) \
|
||||
</span> \
|
||||
</div>';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@endsection
|
|
@ -0,0 +1,203 @@
|
|||
{{-- Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com> --}}
|
||||
|
||||
{{-- 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')
|
||||
<h1>{{ $server->name }}<small>{{ $server->uuid }}</small></h1>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{{ route('admin.index') }}">Admin</a></li>
|
||||
<li><a href="{{ route('admin.servers') }}">Servers</a></li>
|
||||
<li class="active">{{ $server->name }}</li>
|
||||
</ol>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="nav-tabs-custom nav-tabs-floating">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active"><a href="{{ route('admin.servers.view', $server->id) }}">About</a></li>
|
||||
@if(! $server->trashed() && $server->installed === 1)
|
||||
<li><a href="{{ route('admin.servers.view.details', $server->id) }}">Details</a></li>
|
||||
<li><a href="{{ route('admin.servers.view.build', $server->id) }}">Build Configuration</a></li>
|
||||
<li><a href="{{ route('admin.servers.view.startup', $server->id) }}">Startup</a></li>
|
||||
<li><a href="{{ route('admin.servers.view.database', $server->id) }}">Database</a></li>
|
||||
@endif
|
||||
@if(! $server->trashed())
|
||||
<li><a href="{{ route('admin.servers.view.manage', $server->id) }}">Manage</a></li>
|
||||
@endif
|
||||
<li class="tab-danger"><a href="{{ route('admin.servers.view.delete', $server->id) }}">Delete</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-8">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="box box-primary">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Information</h3>
|
||||
</div>
|
||||
<div class="box-body table-responsive no-padding">
|
||||
<table class="table table-hover">
|
||||
<tr>
|
||||
<td>UUID</td>
|
||||
<td>{{ $server->uuid }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Docker Container ID</td>
|
||||
<td data-attr="container-id"><i class="fa fa-fw fa-refresh fa-spin"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Docker User ID</td>
|
||||
<td data-attr="container-user"><i class="fa fa-fw fa-refresh fa-spin"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Service</td>
|
||||
<td>{{ $server->option->service->name }} :: {{ $server->option->name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>{{ $server->name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Memory</td>
|
||||
<td><code>{{ $server->memory }}MB</code> / <code data-toggle="tooltip" data-placement="top" title="Swap Space">{{ $server->swap }}MB</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><abbr title="Out of Memory">OOM</abbr> Killer</td>
|
||||
<td>{!! ($server->oom_disabled === 0) ? '<span class="label label-success">Enabled</span>' : '<span class="label label-default">Disabled</span>' !!}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Disk Space</td>
|
||||
<td><code>{{ $server->disk }}MB</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Block IO Weight</td>
|
||||
<td><code>{{ $server->io }}</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>CPU Limit</td>
|
||||
<td><code>{{ $server->cpu }}%</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Default Connection</td>
|
||||
<td><code>{{ $server->allocation->ip }}:{{ $server->allocation->port }}</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Connection Alias</td>
|
||||
<td>
|
||||
@if($server->allocation->alias !== $server->allocation->ip)
|
||||
<code>{{ $server->allocation->alias }}:{{ $server->allocation->port }}</code>
|
||||
@else
|
||||
<span class="label label-default">No Alias Assigned</span>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="box box-primary">
|
||||
<div class="box-body" style="padding-bottom: 0px;">
|
||||
<div class="row">
|
||||
@if($server->suspended)
|
||||
<div class="col-sm-12">
|
||||
<div class="small-box bg-yellow">
|
||||
<div class="inner">
|
||||
<h3 class="no-margin">Suspended</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@if($server->installed !== 1)
|
||||
<div class="col-sm-12">
|
||||
<div class="small-box {{ (! $server->installed) ? 'bg-blue' : 'bg-maroon' }}">
|
||||
<div class="inner">
|
||||
<h3 class="no-margin">{{ (! $server->installed) ? 'Installing' : 'Install Failed' }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
<div class="col-sm-12">
|
||||
<div class="small-box bg-gray">
|
||||
<div class="inner">
|
||||
<h3>{{ str_limit($server->user->username, 8) }}</h3>
|
||||
<p>Server Owner</p>
|
||||
</div>
|
||||
<div class="icon"><i class="fa fa-user"></i></div>
|
||||
<a href="{{ route('admin.users.view', $server->user->id) }}" class="small-box-footer">
|
||||
More info <i class="fa fa-arrow-circle-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<div class="small-box bg-gray">
|
||||
<div class="inner">
|
||||
<h3>{{ str_limit($server->node->name, 8) }}</h3>
|
||||
<p>Server Node</p>
|
||||
</div>
|
||||
<div class="icon"><i class="fa fa-codepen"></i></div>
|
||||
<a href="{{ route('admin.nodes.view', $server->node->id) }}" class="small-box-footer">
|
||||
More info <i class="fa fa-arrow-circle-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('footer-scripts')
|
||||
@parent
|
||||
<script>
|
||||
(function checkServerInfo() {
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
headers: {
|
||||
'X-Access-Token': '{{ $server->daemonSecret }}',
|
||||
'X-Access-Server': '{{ $server->uuid }}'
|
||||
},
|
||||
url: '{{ $server->node->scheme }}://{{ $server->node->fqdn }}:{{ $server->node->daemonListen }}/server',
|
||||
dataType: 'json',
|
||||
timeout: 5000,
|
||||
}).done(function (data) {
|
||||
$('td[data-attr="container-id"]').html('<code>' + data.container.id + '</code>');
|
||||
$('td[data-attr="container-user"]').html('<code>' + data.user + '</code>');
|
||||
}).fail(function (jqXHR) {
|
||||
$('td[data-attr="container-id"]').html('<code>error</code>');
|
||||
$('td[data-attr="container-user"]').html('<code>error</code>');
|
||||
console.error(jqXHR);
|
||||
}).always(function () {
|
||||
setTimeout(checkServerInfo, 60000);
|
||||
})
|
||||
})();
|
||||
</script>
|
||||
@endsection
|
|
@ -0,0 +1,127 @@
|
|||
{{-- Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com> --}}
|
||||
|
||||
{{-- 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')
|
||||
<h1>{{ $server->name }}<small>Additional actions to control this server.</small></h1>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{{ route('admin.index') }}">Admin</a></li>
|
||||
<li><a href="{{ route('admin.servers') }}">Servers</a></li>
|
||||
<li><a href="{{ route('admin.servers.view', $server->id) }}">{{ $server->name }}</a></li>
|
||||
<li class="active">Manage</li>
|
||||
</ol>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="nav-tabs-custom nav-tabs-floating">
|
||||
<ul class="nav nav-tabs">
|
||||
<li><a href="{{ route('admin.servers.view', $server->id) }}">About</a></li>
|
||||
@if(! $server->trashed() && $server->installed === 1)
|
||||
<li><a href="{{ route('admin.servers.view.details', $server->id) }}">Details</a></li>
|
||||
<li><a href="{{ route('admin.servers.view.build', $server->id) }}">Build Configuration</a></li>
|
||||
<li><a href="{{ route('admin.servers.view.startup', $server->id) }}">Startup</a></li>
|
||||
<li><a href="{{ route('admin.servers.view.database', $server->id) }}">Database</a></li>
|
||||
@endif
|
||||
@if(! $server->trashed())
|
||||
<li class="active"><a href="{{ route('admin.servers.view.manage', $server->id) }}">Manage</a></li>
|
||||
@endif
|
||||
<li class="tab-danger"><a href="{{ route('admin.servers.view.delete', $server->id) }}">Delete</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
<div class="box box-primary">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Install Status</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<p>If you need to change the install status from uninstalled to installed, or vice versa, you may do so with the button below.</p>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<form action="{{ route('admin.servers.view.manage.toggle', $server->id) }}" method="POST">
|
||||
{!! csrf_field() !!}
|
||||
<button type="submit" class="btn btn-primary">Toggle Install Status</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Rebuild Container</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<p>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.</p>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<form action="{{ route('admin.servers.view.manage.rebuild', $server->id) }}" method="POST">
|
||||
{!! csrf_field() !!}
|
||||
<button type="submit" class="btn btn-default">Rebuild Server Container</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if(! $server->suspended)
|
||||
<div class="col-sm-4">
|
||||
<div class="box box-warning">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Suspend Server</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<p>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.</p>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<form action="{{ route('admin.servers.view.manage.suspension', $server->id) }}" method="POST">
|
||||
{!! csrf_field() !!}
|
||||
<input type="hidden" name="action" value="suspend" />
|
||||
<button type="submit" class="btn btn-warning">Suspend Server</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<div class="col-sm-4">
|
||||
<div class="box box-success">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Unsuspend Server</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<p>This will unsuspend the server and restore normal user access.</p>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<form action="{{ route('admin.servers.view.manage.suspension', $server->id) }}" method="POST">
|
||||
{!! csrf_field() !!}
|
||||
<input type="hidden" name="action" value="unsuspend" />
|
||||
<button type="submit" class="btn btn-success">Unsuspend Server</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endsection
|
|
@ -0,0 +1,115 @@
|
|||
{{-- Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com> --}}
|
||||
|
||||
{{-- 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')
|
||||
<h1>{{ $server->name }}<small>Control startup command as well as variables.</small></h1>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{{ route('admin.index') }}">Admin</a></li>
|
||||
<li><a href="{{ route('admin.servers') }}">Servers</a></li>
|
||||
<li><a href="{{ route('admin.servers.view', $server->id) }}">{{ $server->name }}</a></li>
|
||||
<li class="active">Startup</li>
|
||||
</ol>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="nav-tabs-custom nav-tabs-floating">
|
||||
<ul class="nav nav-tabs">
|
||||
<li><a href="{{ route('admin.servers.view', $server->id) }}">About</a></li>
|
||||
@if(! $server->trashed() && $server->installed === 1)
|
||||
<li><a href="{{ route('admin.servers.view.details', $server->id) }}">Details</a></li>
|
||||
<li><a href="{{ route('admin.servers.view.build', $server->id) }}">Build Configuration</a></li>
|
||||
<li class="active"><a href="{{ route('admin.servers.view.startup', $server->id) }}">Startup</a></li>
|
||||
<li><a href="{{ route('admin.servers.view.database', $server->id) }}">Database</a></li>
|
||||
@endif
|
||||
@if(! $server->trashed())
|
||||
<li><a href="{{ route('admin.servers.view.manage', $server->id) }}">Manage</a></li>
|
||||
@endif
|
||||
<li class="tab-danger"><a href="{{ route('admin.servers.view.delete', $server->id) }}">Delete</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form action="{{ route('admin.servers.view.startup', $server->id) }}" method="POST">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="box box-primary">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Startup Command Modification</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<label for="pStartup" class="form-label">Startup Command</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon bg-gray">{{ $server->option->display_executable }}</span>
|
||||
<input id="pStartup" name="startup" class="form-control" type="text" value="{{ old('startup', $server->startup) }}" />
|
||||
</div>
|
||||
<p class="small text-muted">Edit your server's startup command here. The following variables are available by default: <code>@{{SERVER_MEMORY}}</code>, <code>@{{SERVER_IP}}</code>, and <code>@{{SERVER_PORT}}</code>.</p>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
{!! csrf_field() !!}
|
||||
<button type="submit" class="btn btn-primary btn-sm pull-right">Save Modifications</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@foreach($server->option->variables as $variable)
|
||||
<div class="col-xs-12 col-md-4 col-sm-6">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">{{ $variable->name }}</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<input data-action="match-regex" data-regex="{{ $variable->regex }}" name="env_{{ $variable->id }}" class="form-control" type="text" value="{{ old('env_' . $variable->id, $variable->server_value) }}" />
|
||||
<p class="no-margin small text-muted">{{ $variable->description }}</p>
|
||||
<p class="no-margin">
|
||||
@if($variable->required)<span class="label label-danger">Required</span>@else<span class="label label-default">Optional</span>@endif
|
||||
@if($variable->user_viewable)<span class="label label-success">Visible</span>@else<span class="label label-primary">Hidden</span>@endif
|
||||
@if($variable->user_editable)<span class="label label-success">Editable</span>@else<span class="label label-primary">Locked</span>@endif
|
||||
</p>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<p class="no-margin text-muted small"><strong>Startup Command Variable:</strong> <code>{{ $variable->env_variable }}</code></p>
|
||||
<p class="no-margin text-muted small"><strong>Verification Regex:</strong> <code>{{ $variable->regex }}</code></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</form>
|
||||
@endsection
|
||||
|
||||
@section('footer-scripts')
|
||||
@parent
|
||||
<script>
|
||||
$('input[data-action="match-regex"]').on('keyup', function (event) {
|
||||
if (! $(this).data('regex')) return;
|
||||
|
||||
var input = $(this).val();
|
||||
var regex = new RegExp($(this).data('regex').replace(/^\/|\/$/g, ''));
|
||||
|
||||
$(this).parent().parent().removeClass('has-success has-error').addClass((! regex.test(input)) ? 'has-error' : 'has-success');
|
||||
});
|
||||
</script>
|
||||
@endsection
|
|
@ -0,0 +1,100 @@
|
|||
{{-- Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com> --}}
|
||||
|
||||
{{-- 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')
|
||||
<h1>Panel Settings<small>Configure Pterodactyl to your liking.</small></h1>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{{ route('admin.index') }}">Admin</a></li>
|
||||
<li class="active">Settings</li>
|
||||
</ol>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Panel Settings</h3>
|
||||
</div>
|
||||
<form action="{{ route('admin.settings') }}" method="POST">
|
||||
<div class="box-body">
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
<label class="control-label">Company Name:</label>
|
||||
<div>
|
||||
<input type="text" class="form-control" name="company" value="{{ old('company', Settings::get('company')) }}" />
|
||||
<p class="text-muted"><small>This is the name that is used throughout the panel and in emails sent to clients.</small></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<label class="control-label">Default Language:</label>
|
||||
<div>
|
||||
<select name="default_language" class="form-control">
|
||||
<option value="de" @if(Settings::get('default_language') === 'de')selected @endif>Deutsch</option>
|
||||
<option value="en" @if(Settings::get('default_language', 'en') === 'en')selected @endif>English</option>
|
||||
<option value="es" @if(Settings::get('default_language') === 'es')selected @endif>Español</option>
|
||||
<option value="fr" @if(Settings::get('default_language') === 'fr')selected @endif>Français</option>
|
||||
<option value="it" @if(Settings::get('default_language') === 'it')selected @endif>Italiano</option>
|
||||
<option value="pl" @if(Settings::get('default_language') === 'pl')selected @endif>Polski</option>
|
||||
<option value="pt" @if(Settings::get('default_language') === 'pt')selected @endif>Português</option>
|
||||
<option value="ru" @if(Settings::get('default_language') === 'ru')selected @endif>русский</option>
|
||||
<option value="se" @if(Settings::get('default_language') === 'se')selected @endif>Svenska</option>
|
||||
<option value="zh" @if(Settings::get('default_language') === 'zh')selected @endif>中国的的</option>
|
||||
</select>
|
||||
<p class="text-muted"><small>This is the default language that all clients will use unless they manually change it.</small></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="alert alert-info">In order to modify your SMTP settings for sending mail you will need to edit the <code>.env</code> file in this project's root folder.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
<label class="control-label">Send Emails From:</label>
|
||||
<div>
|
||||
<input type="text" class="form-control" name="email_from" value="{{ old('email_from', Settings::get('email_from', env('MAIL_FROM', 'you@example.com'))) }}" />
|
||||
<p class="text-muted"><small>The email address that panel emails will be sent from. Note that some SMTP services require this to match for a given API key.</small></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<label class="control-label">Email Sender Name:</label>
|
||||
<div>
|
||||
<input type="text" class="form-control" name="email_sender_name" value="{{ old('email_sender_name', Settings::get('email_sender_name', env('MAIL_FROM_NAME', 'Pterodactyl Panel'))) }}" />
|
||||
<p class="text-muted"><small>The name that emails will appear to come from.</small></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
{!! csrf_field() !!}
|
||||
<input type="submit" class="btn btn-sm btn-primary" value="Modify Settings">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
|
@ -0,0 +1,84 @@
|
|||
{{-- Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com> --}}
|
||||
|
||||
{{-- 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')
|
||||
<h1>Users<small>All registered users on the system.</small></h1>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{{ route('admin.index') }}">Admin</a></li>
|
||||
<li class="active">Users</li>
|
||||
</ol>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="box box-primary">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">User List</h3>
|
||||
<div class="box-tools">
|
||||
<form action="{{ route('admin.users') }}" method="GET">
|
||||
<div class="input-group input-group-sm" style="width: 300px;">
|
||||
<input type="text" name="query" class="form-control pull-right" value="{{ request()->input('query') }}" placeholder="Search">
|
||||
<div class="input-group-btn">
|
||||
<button type="submit" class="btn btn-default"><i class="fa fa-search"></i></button>
|
||||
<a href="{{ route('admin.users.new') }}"><button type="button" class="btn btn-sm btn-primary" style="border-radius: 0 3px 3px 0;margin-left:-1px;">Create New</button></a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-body table-responsive no-padding">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</td>
|
||||
<th>Email</td>
|
||||
<th>Client Name</th>
|
||||
<th>Username</th>
|
||||
<th>Servers</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($users as $user)
|
||||
<tr class="align-middle">
|
||||
<td><code>{{ $user->id }}</code></td>
|
||||
<td><a href="{{ route('admin.users.view', $user->id) }}">{{ $user->email }}</a></td>
|
||||
<td>{{ $user->name_last }}, {{ $user->name_first }}</td>
|
||||
<td>{{ $user->username }}</td>
|
||||
<td>{{ $user->servers_count }}</td>
|
||||
<td class="text-center"><img src="https://www.gravatar.com/avatar/{{ md5(strtolower($user->email)) }}?s=20" class="img-circle" /></td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="box-footer with-border">
|
||||
<div class="col-md-12 text-center">{!! $users->render() !!}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
|
@ -0,0 +1,139 @@
|
|||
{{-- Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com> --}}
|
||||
|
||||
{{-- 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')
|
||||
<h1>Create User<small>Add a new user to the system.</small></h1>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{{ route('admin.index') }}">Admin</a></li>
|
||||
<li><a href="{{ route('admin.users') }}">Users</a></li>
|
||||
<li class="active">Create</li>
|
||||
</ol>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<form method="post">
|
||||
<div class="col-xs-6">
|
||||
<div class="box box-primary">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Identity</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="form-group">
|
||||
<label for="email" class="control-label">Email</label>
|
||||
<div>
|
||||
<input type="text" autocomplete="off" name="email" value="{{ old('email') }}" class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="username" class="control-label">Username</label>
|
||||
<div>
|
||||
<input type="text" autocomplete="off" name="username" value="{{ old('username') }}" class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="name_first" class="control-label">Client First Name</label>
|
||||
<div>
|
||||
<input type="text" autocomplete="off" name="name_first" value="{{ old('name_first') }}" class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="name_last" class="control-label">Client Last Name</label>
|
||||
<div>
|
||||
<input type="text" autocomplete="off" name="name_last" value="{{ old('name_last') }}" class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
{!! csrf_field() !!}
|
||||
<input type="submit" value="Create User" class="btn btn-success btn-sm">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-6">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Permissions</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="form-group col-md-12">
|
||||
<label for="root_admin" class="control-label">Administrator</label>
|
||||
<div>
|
||||
<select name="root_admin" class="form-control">
|
||||
<option value="0">{{ trans('strings.no') }}</option>
|
||||
<option value="1">{{ trans('strings.yes') }}</option>
|
||||
</select>
|
||||
<p class="text-muted"><small>Setting this to 'Yes' gives a user full administrative access.</small></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-6">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Password</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="alert alert-info">
|
||||
<p>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.</p>
|
||||
</div>
|
||||
<div id="gen_pass" class=" alert alert-success" style="display:none;margin-bottom: 10px;"></div>
|
||||
<div class="form-group">
|
||||
<label for="pass" class="control-label">Password</label>
|
||||
<div>
|
||||
<input type="password" name="password" class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button class="btn btn-default btn-sm" id="gen_pass_bttn" type="button">Generate Password</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('footer-scripts')
|
||||
@parent
|
||||
<script>$("#gen_pass_bttn").click(function (event) {
|
||||
event.preventDefault();
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "/password-gen/12",
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||
},
|
||||
success: function(data) {
|
||||
$("#gen_pass").html('<strong>Generated Password:</strong> ' + data).slideDown();
|
||||
$('input[name="password"], input[name="password_confirmation"]').val(data);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
</script>
|
||||
@endsection
|
|
@ -1,5 +1,4 @@
|
|||
{{-- Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com> --}}
|
||||
{{-- Some Modifications (c) 2015 Dylan Seidt <dylan.seidt@gmail.com> --}}
|
||||
|
||||
{{-- 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')
|
||||
<h1>{{ $user->name_first }} {{ $user->name_last}}<small>{{ $user->username }}</small></h1>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{{ route('admin.index') }}">Admin</a></li>
|
||||
<li><a href="{{ route('admin.users') }}">Users</a></li>
|
||||
<li class="active">{{ $user->username }}</li>
|
||||
</ol>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<div class="col-md-12">
|
||||
<ul class="breadcrumb">
|
||||
<li><a href="/admin">Admin Controls</a></li>
|
||||
<li><a href="/admin/users">Accounts</a></li>
|
||||
<li class="active">{{ $user->email }}</li>
|
||||
</ul>
|
||||
<h3 style="margin-bottom: 5px;">Viewing User: {{ $user->email }}</h3>
|
||||
<p class="text-muted" style="margin: 0 0 -10.5px !important;"><small>Registered {{ (new Carbon($user->created_at))->toRfc1123String() }}</small></p>
|
||||
<hr />
|
||||
<div class="row">
|
||||
<form action="{{ route('admin.users.view', $user->id) }}" method="post">
|
||||
<div class="col-md-6">
|
||||
<fieldset>
|
||||
<div class="col-xs-6">
|
||||
<div class="box box-primary">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Identity</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="form-group">
|
||||
<label for="email" class="control-label">Email</label>
|
||||
<div>
|
||||
|
@ -62,14 +65,19 @@
|
|||
<input type="text" name="name_last" value="{{ $user->name_last }}" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
{!! csrf_field() !!}
|
||||
<input type="submit" value="Update User" class="btn btn-primary btn-sm">
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="well" style="padding-bottom: 0;">
|
||||
</div>
|
||||
<div class="col-xs-6">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Password</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="alert alert-success" style="display:none;margin-bottom:10px;" id="gen_pass"></div>
|
||||
<div class="form-group">
|
||||
<label for="password" class="control-label">Password</label>
|
||||
|
@ -81,76 +89,83 @@
|
|||
<button class="btn btn-default btn-sm" id="gen_pass_bttn" type="button">Generate Password</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="well" style="padding-bottom: 0;">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-6">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Permissions</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="form-group">
|
||||
<label for="root_admin" class="control-label">Administrator</label>
|
||||
<div>
|
||||
<select name="root_admin" class="form-control">
|
||||
<option value="0">{{ trans('strings.no') }}</option>
|
||||
<option value="1" @if($user->root_admin)selected="selected"@endif>{{ trans('strings.yes') }}</option>
|
||||
<option value="1" {{ $user->root_admin ? 'selected="selected"' : '' }}>{{ trans('strings.yes') }}</option>
|
||||
</select>
|
||||
<p class="text-muted"><small>Setting this to 'Yes' gives a user full administrative access.</small></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>Associated Servers</h3><hr>
|
||||
@if($user->servers)
|
||||
<table class="table table-striped table-bordered table-hover">
|
||||
</form>
|
||||
<div class="col-xs-12">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Associated Servers</h3>
|
||||
</div>
|
||||
<div class="box-body table-responsive no-padding">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:2%;"></th>
|
||||
<th>Identifier</th>
|
||||
<th>Server Name</th>
|
||||
<th>Node</th>
|
||||
<th>Username</th>
|
||||
<th style="width:10%;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($user->servers as $server)
|
||||
<tr>
|
||||
<td><a href="/server/{{ $server->uuidShort }}/"><i class="fa fa-tachometer"></i></a></td>
|
||||
<td><a href="{{ route('server.index', $server->uuidShort) }}/"><i class="fa fa-tachometer"></i></a></td>
|
||||
<td><code>{{ $server->uuidShort }}</code></td>
|
||||
<td><a href="/admin/servers/view/{{ $server->id }}">{{ $server->name }}</a></td>
|
||||
<td>{{ $server->node->name }}</td>
|
||||
<td><code>{{ $server->username }}</code></td>
|
||||
<td><a href="{{ route('admin.servers.view', $server->id) }}">{{ $server->name }}</a></td>
|
||||
<td><a href="{{ route('admin.nodes.view', $server->node->id) }}">{{ $server->node->name }}</a></td>
|
||||
<td class="centered">@if($server->suspended === 0)<span class="label muted muted-hover label-success">Active</span>@else<span class="label label-warning">Suspended</span>@endif</td>
|
||||
</td>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@else
|
||||
<div class="alert alert-info">There are no servers associated with this account.</div>
|
||||
@endif
|
||||
<a href="/admin/servers/new?email={{ $user->email }}"><button type="button" class="btn btn-success btn-sm">Add New Server</button></a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>Delete Account</h3><hr />
|
||||
<div class="alert alert-danger"><strong>Warning!</strong> There most be no servers associated with this account in order for it to be deleted.</div>
|
||||
<div class="col-xs-12">
|
||||
<div class="box box-danger">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Delete User</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<p class="no-margin">There most be no servers associated with this account in order for it to be deleted.</p>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<form action="{{ route('admin.users.view', $user->id) }}" method="POST">
|
||||
{!! method_field('DELETE') !!}
|
||||
{!! csrf_field() !!}
|
||||
<input type="submit" class="btn btn-sm btn-danger pull-right" value="Delete User" />
|
||||
{!! method_field('DELETE') !!}
|
||||
<input id="delete" type="submit" class="btn btn-sm btn-danger pull-right" {{ $user->servers->count() < 1 ?: 'disabled' }} value="Delete User" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$("#sidebar_links").find("a[href='/admin/users']").addClass('active');
|
||||
$('#delete').click(function() {
|
||||
if(confirm('{{ trans('base.confirm') }}')) {
|
||||
$('#delete').load($(this).attr('href'));
|
||||
}
|
||||
});
|
||||
$("#gen_pass_bttn").click(function (event) {
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('footer-scripts')
|
||||
@parent
|
||||
<script>$("#gen_pass_bttn").click(function (event) {
|
||||
event.preventDefault();
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
|
@ -159,13 +174,12 @@ $(document).ready(function(){
|
|||
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||
},
|
||||
success: function(data) {
|
||||
$("#gen_pass").html('You must click <em>Update User</em> to the left for this password to be applied.<br /><br /><strong>Generated Password:</strong> ' + data).slideDown();
|
||||
$("#gen_pass").html('You must click <em>Update Password</em> below for this password to be applied.<br /><br /><strong>Generated Password:</strong> ' + data).slideDown();
|
||||
$('input[name="password"], input[name="password_confirmation"]').val(data);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
|
@ -0,0 +1,204 @@
|
|||
{{-- Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com> --}}
|
||||
|
||||
{{-- 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. --}}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>{{ Settings::get('company', 'Pterodactyl') }} - @yield('title')</title>
|
||||
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
|
||||
<meta name="_token" content="{{ csrf_token() }}">
|
||||
@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') !!}
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/ionicons/2.0.1/css/ionicons.min.css">
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
|
||||
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
|
||||
<![endif]-->
|
||||
@show
|
||||
</head>
|
||||
<body class="hold-transition skin-blue fixed sidebar-mini">
|
||||
<div class="wrapper">
|
||||
<header class="main-header">
|
||||
<a href="{{ route('index') }}" class="logo">
|
||||
<span>{{ Settings::get('company', 'Pterodactyl') }}</span>
|
||||
</a>
|
||||
<nav class="navbar navbar-static-top">
|
||||
<a href="#" class="sidebar-toggle" data-toggle="offcanvas" role="button">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</a>
|
||||
<div class="navbar-custom-menu">
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="dropdown user-menu">
|
||||
<a href="{{ route('account') }}" class="dropdown-toggle" data-toggle="dropdown">
|
||||
<img src="https://www.gravatar.com/avatar/{{ md5(strtolower(Auth::user()->email)) }}?s=160" class="user-image" alt="User Image">
|
||||
<span class="hidden-xs">{{ Auth::user()->name_first }} {{ Auth::user()->name_last }}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" data-action="control-sidebar" data-toggle="tooltip" data-placement="bottom" title="Quick Access"><i class="fa fa-fighter-jet" style="margin-top:4px;padding-bottom:2px;"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<li><a href="{{ route('index') }}" data-toggle="tooltip" data-placement="bottom" title="Exit Admin Control"><i class="fa fa-server" style="margin-top:4px;padding-bottom:2px;"></i></a></li>
|
||||
</li>
|
||||
<li>
|
||||
<li><a href="{{ route('auth.logout') }}" data-toggle="tooltip" data-placement="bottom" title="Logout"><i class="fa fa-power-off" style="margin-top:4px;padding-bottom:2px;"></i></a></li>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
<aside class="main-sidebar">
|
||||
<section class="sidebar">
|
||||
<ul class="sidebar-menu">
|
||||
<li class="header">BASIC ADMINISTRATION</li>
|
||||
<li class="{{ Route::currentRouteName() !== 'admin.index' ?: 'active' }}">
|
||||
<a href="{{ route('admin.index') }}">
|
||||
<i class="fa fa-home"></i> <span>Overview</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="{{ ! starts_with(Route::currentRouteName(), 'admin.settings') ?: 'active' }}">
|
||||
<a href="{{ route('admin.settings')}}">
|
||||
<i class="fa fa-wrench"></i> <span>Settings</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="header">MANAGEMENT</li>
|
||||
<li class="{{ ! starts_with(Route::currentRouteName(), 'admin.servers') ?: 'active' }}">
|
||||
<a href="{{ route('admin.servers') }}">
|
||||
<i class="fa fa-server"></i> <span>Servers</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="{{ ! starts_with(Route::currentRouteName(), 'admin.nodes') ?: 'active' }}">
|
||||
<a href="{{ route('admin.nodes') }}">
|
||||
<i class="fa fa-sitemap"></i> <span>Nodes</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="{{ ! starts_with(Route::currentRouteName(), 'admin.users') ?: 'active' }}">
|
||||
<a href="{{ route('admin.users') }}">
|
||||
<i class="fa fa-users"></i> <span>Users</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</aside>
|
||||
<div class="content-wrapper">
|
||||
<section class="content-header">
|
||||
@yield('content-header')
|
||||
</section>
|
||||
<section class="content">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
@if (count($errors) > 0)
|
||||
<div class="callout callout-danger">
|
||||
@lang('base.validation_error')<br><br>
|
||||
<ul>
|
||||
@foreach ($errors->all() as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
@foreach (Alert::getMessages() as $type => $messages)
|
||||
@foreach ($messages as $message)
|
||||
<div class="callout callout-{{ $type }} alert-dismissable" role="alert">
|
||||
{!! $message !!}
|
||||
</div>
|
||||
@endforeach
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@yield('content')
|
||||
</section>
|
||||
</div>
|
||||
<footer class="main-footer">
|
||||
<div class="pull-right hidden-xs small text-gray">
|
||||
<strong>v</strong> {{ config('app.version') }}
|
||||
</div>
|
||||
Copyright © 2015 - {{ date('Y') }} <a href="https://pterodactyl.io/">Pterodactyl Software</a>.
|
||||
</footer>
|
||||
<aside class="control-sidebar control-sidebar-dark">
|
||||
<ul class="nav nav-tabs nav-justified control-sidebar-tabs">
|
||||
<li class="active"><a href="#control-sidebar-servers-tab" data-toggle="tab"><i class="fa fa-server"></i></a></li>
|
||||
<li><a href="#control-sidebar-nodes-tab" data-toggle="tab"><i class="fa fa-sitemap"></i></a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="control-sidebar-servers-tab">
|
||||
<ul class="control-sidebar-menu">
|
||||
@foreach (Pterodactyl\Models\Server::all() as $s)
|
||||
<li>
|
||||
<a href="{{ route('admin.servers.view', $s->id) }}">
|
||||
@if($s->owner_id === Auth::user()->id)
|
||||
<i class="menu-icon fa fa-user bg-blue"></i>
|
||||
@else
|
||||
<i class="menu-icon fa fa-user-o bg-gray"></i>
|
||||
@endif
|
||||
<div class="menu-info">
|
||||
<h4 class="control-sidebar-subheading">{{ $s->name }}</h4>
|
||||
<p>{{ $s->username }}</p>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-pane" id="control-sidebar-nodes-tab">
|
||||
<ul class="control-sidebar-menu">
|
||||
@foreach (Pterodactyl\Models\Node::with('location')->get() as $n)
|
||||
<li>
|
||||
<a href="{{ route('admin.nodes.view', $n->id) }}">
|
||||
<i class="menu-icon fa fa-codepen bg-gray"></i>
|
||||
<div class="menu-info">
|
||||
<h4 class="control-sidebar-subheading">{{ $n->name }}</h4>
|
||||
<p>{{ $n->location->short }}</p>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<div class="control-sidebar-bg"></div>
|
||||
</div>
|
||||
@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
|
||||
</body>
|
||||
</html>
|
|
@ -62,14 +62,14 @@
|
|||
<span class="hidden-xs">{{ Auth::user()->name_first }} {{ Auth::user()->name_last }}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" data-action="control-sidebar" data-toggle="tooltip" data-placement="bottom" title="{{ @trans('strings.servers') }}"><i class="fa fa-server" style="margin-top:4px;padding-bottom:2px;"></i></a>
|
||||
</li>
|
||||
@if(Auth::user()->isRootAdmin())
|
||||
<li>
|
||||
<li><a href="{{ route('admin.index') }}" data-toggle="tooltip" data-placement="bottom" title="{{ @trans('strings.admin_cp') }}"><i class="fa fa-gears" style="margin-top:4px;padding-bottom:2px;"></i></a></li>
|
||||
</li>
|
||||
@endif
|
||||
<li>
|
||||
<a href="#" data-action="control-sidebar" data-toggle="tooltip" data-placement="bottom" title="{{ @trans('strings.servers') }}"><i class="fa fa-server" style="margin-top:4px;padding-bottom:2px;"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<li><a href="{{ route('auth.logout') }}" data-toggle="tooltip" data-placement="bottom" title="{{ @trans('strings.logout') }}"><i class="fa fa-power-off" style="margin-top:4px;padding-bottom:2px;"></i></a></li>
|
||||
</li>
|
||||
|
|
|
@ -49,17 +49,19 @@
|
|||
<th>@lang('strings.username')</th>
|
||||
<th>@lang('strings.password')</th>
|
||||
<th>@lang('server.config.database.host')</th>
|
||||
@can('reset-db-password', $server)<td></td>@endcan
|
||||
</tr>
|
||||
@foreach($databases as $database)
|
||||
<tr>
|
||||
<td>{{ $database->database }}</td>
|
||||
<td>{{ $database->username }}</td>
|
||||
<td><code>{{ Crypt::decrypt($database->password) }}</code>
|
||||
<td class="middle">{{ $database->database }}</td>
|
||||
<td class="middle">{{ $database->username }}</td>
|
||||
<td class="middle"><code data-attr="set-password">{{ Crypt::decrypt($database->password) }}</code></td>
|
||||
<td class="middle"><code>{{ $database->a_host }}:{{ $database->a_port }}</code></td>
|
||||
@can('reset-db-password', $server)
|
||||
<button class="btn btn-xs btn-primary pull-right" data-action="reset-database-password" data-id="{{ $database->id }}"><i class="fa fa-fw fa-refresh"></i> @lang('server.config.database.reset_password')</button>
|
||||
@endcan
|
||||
<td>
|
||||
<button class="btn btn-xs btn-primary pull-right" data-action="reset-password" data-id="{{ $database->id }}"><i class="fa fa-fw fa-refresh"></i> @lang('server.config.database.reset_password')</button>
|
||||
</td>
|
||||
<td><code>{{ $database->a_host }}:{{ $database->a_port }}</code></td>
|
||||
@endcan
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
|
@ -88,10 +90,10 @@
|
|||
{!! Theme::js('js/frontend/server.socket.js') !!}
|
||||
<script>
|
||||
@can('reset-db-password', $server)
|
||||
$('[data-action="reset-database-password"]').click(function (e) {
|
||||
$('[data-action="reset-password"]').click(function (e) {
|
||||
e.preventDefault();
|
||||
var block = $(this);
|
||||
$(this).find('i').addClass('fa-spin');
|
||||
$(this).addClass('disabled').find('i').addClass('fa-spin');
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: Router.route('server.ajax.reset-database-password', { server: Pterodactyl.server.uuidShort }),
|
||||
|
@ -99,10 +101,10 @@
|
|||
'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'),
|
||||
},
|
||||
data: {
|
||||
'database': $(this).data('id')
|
||||
database: $(this).data('id')
|
||||
}
|
||||
}).done(function (data) {
|
||||
block.parent().find('code').html(data);
|
||||
block.parent().parent().find('[data-attr="set-password"]').html(data);
|
||||
}).fail(function(jqXHR, textStatus, errorThrown) {
|
||||
console.error(jqXHR);
|
||||
var error = 'An error occured while trying to process this request.';
|
||||
|
@ -115,7 +117,7 @@
|
|||
text: error
|
||||
});
|
||||
}).always(function () {
|
||||
block.find('i').removeClass('fa-spin');
|
||||
block.removeClass('disabled').find('i').removeClass('fa-spin');
|
||||
});
|
||||
});
|
||||
@endcan
|
||||
|
|
|
@ -35,7 +35,8 @@
|
|||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<form action="{{ route('server.settings.startup', $server->uuidShort) }}" method="POST">
|
||||
<div class="col-xs-12">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">@lang('server.config.startup.command')</h3>
|
||||
|
@ -46,53 +47,64 @@
|
|||
<input type="text" class="form-control" readonly="readonly" value="{{ $processedStartup }}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">@lang('server.config.startup.edit_params')</h3>
|
||||
</div>
|
||||
@can('edit-startup', $server)
|
||||
<form action="{{ route('server.settings.startup', $server->uuidShort) }}" method="POST">
|
||||
<div class="box-body">
|
||||
@foreach($variables as $item)
|
||||
<div class="form-group">
|
||||
<label class="control-label">
|
||||
@if($item->required === 1)<span class="label label-danger">@lang('strings.required')</span> @endif
|
||||
{{ $item->name }}
|
||||
</label>
|
||||
<div>
|
||||
<input type="text"
|
||||
@if($item->user_editable === 1)
|
||||
name="{{ $item->env_variable }}"
|
||||
@else
|
||||
readonly="readonly"
|
||||
@endif
|
||||
class="form-control" value="{{ old($item->env_variable, $item->a_serverValue) }}" data-action="matchRegex" data-regex="{{ $item->regex }}" />
|
||||
</div>
|
||||
<p class="text-muted"><small>{!! $item->description !!}</small></p>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
{!! csrf_field() !!}
|
||||
<input type="submit" class="btn btn-primary btn-sm" value="@lang('server.config.startup.update')" />
|
||||
</div>
|
||||
</form>
|
||||
@else
|
||||
<div class="box-body">
|
||||
<div class="callout callout-warning callout-nomargin">
|
||||
<p>@lang('auth.not_authorized')</p>
|
||||
</div>
|
||||
<input type="submit" class="btn btn-primary btn-sm pull-right" value="@lang('server.config.startup.update')" />
|
||||
</div>
|
||||
@endcan
|
||||
</div>
|
||||
</div>
|
||||
@can('edit-startup', $server)
|
||||
@foreach($variables as $variable)
|
||||
<div class="col-xs-12 col-md-4 col-sm-6">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">{{ $variable->name }}</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<input data-action="match-regex" data-regex="{{ $variable->regex }}"
|
||||
@if($variable->user_editable)
|
||||
name="env_{{ $variable->id }}"
|
||||
@else
|
||||
readonly
|
||||
@endif
|
||||
class="form-control" type="text" value="{{ old('env_' . $variable->id, $variable->server_value) }}" />
|
||||
<p class="small text-muted">{{ $variable->description }}</p>
|
||||
<p class="no-margin">
|
||||
@if($variable->required && $variable->user_editable)
|
||||
<span class="label label-danger">@lang('strings.required')</span>
|
||||
@elseif(! $variable->required && $variable->user_editable)
|
||||
<span class="label label-default">@lang('strings.optional')</span>
|
||||
@endif
|
||||
@if(! $variable->user_editable)
|
||||
<span class="label label-warning">@lang('strings.read_only')</span>
|
||||
@endif
|
||||
</p>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<p class="no-margin text-muted small"><strong>@lang('server.config.startup.startup_var'):</strong> <code>{{ $variable->env_variable }}</code></p>
|
||||
<p class="no-margin text-muted small"><strong>@lang('server.config.startup.startup_regex'):</strong> <code>{{ $variable->regex }}</code></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
@endcan
|
||||
</form>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('footer-scripts')
|
||||
@parent
|
||||
{!! Theme::js('js/frontend/server.socket.js') !!}
|
||||
<script>
|
||||
$('input[data-action="match-regex"]').on('keyup', function (event) {
|
||||
if (! $(this).data('regex')) return;
|
||||
|
||||
var input = $(this).val();
|
||||
var regex = new RegExp($(this).data('regex').replace(/^\/|\/$/g, ''));
|
||||
|
||||
$(this).parent().parent().removeClass('has-success has-error').addClass((! regex.test(input)) ? 'has-error' : 'has-success');
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
{{-- Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com> --}}
|
||||
|
||||
{{-- 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')
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<ul class="breadcrumb">
|
||||
<li class="active">Admin Control</li>
|
||||
</ul>
|
||||
<h3 class="nopad">Pterodactyl Admin Control Panel</h3><hr />
|
||||
@if (Version::isLatestPanel())
|
||||
<div class="alert alert-success">You are running Pterodactyl Panel version <code>{{ Version::getCurrentPanel() }}</code>. Your panel is up-to-date!</div>
|
||||
@else
|
||||
<div class="alert alert-danger">
|
||||
Your panel is <strong>not up-to-date!</strong> The latest version is <a href="https://github.com/Pterodactyl/Panel/releases/v{{ Version::getPanel() }}" target="_blank"><code>{{ Version::getPanel() }}</code></a> and you are currently running version <code>{{ Version::getCurrentPanel() }}</code>.
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-4 text-center">
|
||||
<a href="{{ Version::getDiscord() }}"><button class="btn btn-sm btn-warning" style="width:100%;"><i class="fa fa-fw fa-support"></i> Get Help <small>(via Discord)</small></button></a>
|
||||
</div>
|
||||
<div class="col-xs-4 text-center">
|
||||
<a href="https://docs.pterodactyl.io"><button class="btn btn-sm btn-default" style="width:100%;"><i class="fa fa-fw fa-link"></i> Documentation</button></a>
|
||||
</div>
|
||||
<div class="col-xs-4 text-center">
|
||||
<a href="https://github.com/Pterodactyl/Panel"><button class="btn btn-sm btn-default" style="width:100%;"><i class="fa fa-fw fa-support"></i> Github</button></a>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('#sidebar_links').find("a[href='/admin']").addClass('active');
|
||||
});
|
||||
</script>
|
||||
@endsection
|
|
@ -1,96 +0,0 @@
|
|||
{{-- Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com> --}}
|
||||
|
||||
{{-- 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')
|
||||
<div class="col-md-12">
|
||||
<ul class="breadcrumb">
|
||||
<li><a href="/admin">Admin Control</a></li>
|
||||
<li class="active">Nodes</li>
|
||||
</ul>
|
||||
<h3>All Nodes</h3><hr />
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Name</th>
|
||||
<th>Location</th>
|
||||
<th class="hidden-xs">Memory</th>
|
||||
<th class="hidden-xs">Disk</th>
|
||||
<th class="text-center hidden-xs">Servers</th>
|
||||
<th class="text-center">SSL</th>
|
||||
<th class="text-center hidden-xs">Public</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($nodes as $node)
|
||||
<tr>
|
||||
<td class="text-center text-muted left-icon" data-action="ping" data-secret="{{ $node->daemonSecret }}" data-location="{{ $node->scheme }}://{{ $node->fqdn }}:{{ $node->daemonListen }}"><i class="fa fa-fw fa-refresh fa-spin"></i></td>
|
||||
<td><a href="/admin/nodes/view/{{ $node->id }}">{{ $node->name }}</td>
|
||||
<td>{{ $node->location->short }}</td>
|
||||
<td class="hidden-xs">{{ $node->memory }} MB</td>
|
||||
<td class="hidden-xs">{{ $node->disk }} MB</td>
|
||||
<td class="text-center hidden-xs">{{ $node->servers_count }}</td>
|
||||
<td class="text-center" style="color:{{ ($node->scheme === 'https') ? '#50af51' : '#d9534f' }}"><i class="fa fa-{{ ($node->scheme === 'https') ? 'lock' : 'unlock' }}"></i></td>
|
||||
<td class="text-center hidden-xs"><i class="fa fa-{{ ($node->public === 1) ? 'eye' : 'eye-slash' }}"></i></td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-center">{!! $nodes->render() !!}</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('#sidebar_links').find("a[href='/admin/nodes']").addClass('active');
|
||||
(function pingNodes() {
|
||||
$('td[data-action="ping"]').each(function(i, element) {
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: $(element).data('location'),
|
||||
headers: {
|
||||
'X-Access-Token': $(element).data('secret'),
|
||||
},
|
||||
timeout: 5000
|
||||
}).done(function (data) {
|
||||
$(element).find('i').tooltip({
|
||||
title: 'v' + data.version,
|
||||
});
|
||||
$(element).removeClass('text-muted').find('i').removeClass().addClass('fa fa-fw fa-heartbeat faa-pulse animated').css('color', '#50af51');
|
||||
}).fail(function () {
|
||||
$(element).removeClass('text-muted').find('i').removeClass().addClass('fa fa-fw fa-heart-o').css('color', '#d9534f');
|
||||
}).always(function () {
|
||||
setTimeout(pingNodes, 10000);
|
||||
});
|
||||
});
|
||||
})();
|
||||
});
|
||||
</script>
|
||||
@endsection
|
|
@ -1,187 +0,0 @@
|
|||
{{-- Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com> --}}
|
||||
|
||||
{{-- 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')
|
||||
<div class="col-md-12">
|
||||
<ul class="breadcrumb">
|
||||
<li><a href="/admin">Admin Control</a></li>
|
||||
<li><a href="/admin/nodes">Nodes</a></li>
|
||||
<li class="active">Create New Node</li>
|
||||
</ul>
|
||||
<h3>Create New Node</h3><hr />
|
||||
<form action="/admin/nodes/new" method="POST">
|
||||
<div class="well">
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
<label for="name" class="control-label">Node Name</label>
|
||||
<div>
|
||||
<input type="text" autocomplete="off" name="name" class="form-control" value="{{ old('name') }}" />
|
||||
<p class="text-muted"><small>Character limits: <code>a-zA-Z0-9_.-</code> and <code>[Space]</code> (min 1, max 100 characters).</small></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-4">
|
||||
<label for="name" class="control-label">Location</label>
|
||||
<div>
|
||||
<select name="location_id" class="form-control">
|
||||
@foreach($locations as $location)
|
||||
<option value="{{ $location->id }}" {{ (old('location_id') === $location->id) ? 'selected' : '' }}>{{ $location->long }} ({{ $location->short }})</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-2">
|
||||
<label for="public" class="control-label">Public <sup><a data-toggle="tooltip" data-placement="top" title="Allow automatic allocation to this Node?">?</a></sup></label>
|
||||
<div>
|
||||
<input type="radio" name="public" value="1" {{ (old('public') === '1') ? 'checked' : '' }} id="public_1" checked> <label for="public_1" style="padding-left:5px;">Yes</label><br />
|
||||
<input type="radio" name="public" value="0" {{ (old('public') === '0') ? 'checked' : '' }} id="public_0"> <label for="public_0" style="padding-left:5px;">No</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="well">
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
<label for="fqdn" class="control-label">Fully Qualified Domain Name</label>
|
||||
<div>
|
||||
<input type="text" autocomplete="off" name="fqdn" class="form-control" value="{{ old('fqdn') }}" />
|
||||
</div>
|
||||
<p class="text-muted"><small>Please enter domain name (e.g <code>node.example.com</code>) to be used for connecting to the daemon. An IP address may only be used if you are not using SSL for this node.
|
||||
<a tabindex="0" data-toggle="popover" data-trigger="focus" title="Why do I need a FQDN?" data-content="In order to secure communications between your server and this node we use SSL. We cannot generate a SSL certificate for IP Addresses, and as such you will need to provide a FQDN.">Why?</a>
|
||||
</small></p>
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<label for="scheme" class="control-label">Secure Socket Layer</label>
|
||||
<div class="row" style="padding: 7px 0;">
|
||||
<div class="col-xs-6">
|
||||
<input type="radio" name="scheme" value="https" id="scheme_ssl" checked /> <label for="scheme_ssl" style="padding-left: 5px;">Enable HTTPS/SSL</label>
|
||||
</div>
|
||||
<div class="col-xs-6">
|
||||
<input type="radio" name="scheme" value="http" id="scheme_nossl" /> <label for="scheme_nossl" style="padding-left: 5px;">Disable HTTPS/SSL</label>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-muted"><small>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.</small></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="well">
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6 col-xs-6">
|
||||
<label for="memory" class="control-label">Total Memory</label>
|
||||
<div class="input-group">
|
||||
<input type="text" name="memory" data-multiplicator="true" class="form-control" value="{{ old('memory') }}"/>
|
||||
<span class="input-group-addon">MB</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-6 col-xs-6">
|
||||
<label for="memory_overallocate" class="control-label">Overallocate</label>
|
||||
<div class="input-group">
|
||||
<input type="text" name="memory_overallocate" class="form-control" value="{{ old('memory_overallocate', 0) }}"/>
|
||||
<span class="input-group-addon">%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p class="text-muted"><small>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 <code>-1</code> into the field. Entering <code>0</code> will prevent creating new servers if it would put the node over the limit.</small></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="well">
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6 col-xs-6">
|
||||
<label for="disk" class="control-label">Disk Space</label>
|
||||
<div class="input-group">
|
||||
<input type="text" name="disk" data-multiplicator="true" class="form-control" value="{{ old('disk') }}"/>
|
||||
<span class="input-group-addon">MB</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-6 col-xs-6">
|
||||
<label for="disk_overallocate" class="control-label">Overallocate</label>
|
||||
<div class="input-group">
|
||||
<input type="text" name="disk_overallocate" class="form-control" value="{{ old('disk_overallocate', 0) }}"/>
|
||||
<span class="input-group-addon">%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p class="text-muted"><small>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 <code>-1</code> into the field. Entering <code>0</code> will prevent creating new servers if it would put the node over the limit.</small></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="well">
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
<label for="daemonBase" class="control-label">Daemon Server File Location</label>
|
||||
<div>
|
||||
<input type="text" name="daemonBase" class="form-control" value="{{ old('daemonBase', '/srv/daemon-data') }}"/>
|
||||
</div>
|
||||
<p class="text-muted"><small>The location at which your server files will be stored. Most users do not need to change this.</small></p>
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
<label for="daemonListen" class="control-label">Daemon Listening Port</label>
|
||||
<div>
|
||||
<input type="text" name="daemonListen" class="form-control" value="{{ old('daemonListen', '8080') }}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<label for="daemonSFTP" class="control-label">Daemon SFTP Port</label>
|
||||
<div>
|
||||
<input type="text" name="daemonSFTP" class="form-control" value="{{ old('daemonSFTP', '2022') }}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-muted"><small>The daemon runs its own SFTP management container and does not use the SSHd process on the main physical server. <Strong>Do not use the same port that you have assigned for your physcial server's SSH process.</strong></small></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="well">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
{!! csrf_field() !!}
|
||||
<input type="submit" class="btn btn-sm btn-primary" value="Create Node" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('#sidebar_links').find("a[href='/admin/nodes/new']").addClass('active');
|
||||
$('[data-toggle="popover"]').popover({
|
||||
placement: 'auto'
|
||||
});
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
});
|
||||
</script>
|
||||
@endsection
|
|
@ -1,292 +0,0 @@
|
|||
#!/bin/bash
|
||||
####
|
||||
# Pterodactyl - Panel
|
||||
# Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>
|
||||
#
|
||||
# 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
|
|
@ -1,814 +0,0 @@
|
|||
{{-- Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com> --}}
|
||||
|
||||
{{-- 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') !!}
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$.notifyDefaults({
|
||||
placement: {
|
||||
from: 'bottom',
|
||||
align: 'right'
|
||||
},
|
||||
newest_on_top: true,
|
||||
delay: 2000,
|
||||
animate: {
|
||||
enter: 'animated fadeInUp',
|
||||
exit: 'animated fadeOutDown'
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<div class="col-md-12">
|
||||
<ul class="breadcrumb">
|
||||
<li><a href="/admin">Admin Control</a></li>
|
||||
<li><a href="/admin/nodes">Nodes</a></li>
|
||||
<li class="active">{{ $node->name }}</li>
|
||||
</ul>
|
||||
<ul class="nav nav-tabs tabs_with_panel" id="config_tabs">
|
||||
<li class="active"><a href="#tab_about" data-toggle="tab">About</a></li>
|
||||
<li><a href="#tab_settings" data-toggle="tab">Settings</a></li>
|
||||
<li><a href="#tab_configuration" data-toggle="tab">Configuration</a></li>
|
||||
<li><a href="#tab_allocation" data-toggle="tab">Allocation</a></li>
|
||||
<li><a href="#tab_servers" data-toggle="tab">Servers</a></li>
|
||||
@if(count($node->servers) === 0)<li><a href="#tab_delete" data-toggle="tab">Delete</a></li>@endif
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="tab_about">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"></div>
|
||||
<div class="panel-body">
|
||||
<table class="table table-striped" style="margin-bottom:0;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Daemon Version</td>
|
||||
<td><code data-attr="info-version"><i class="fa fa-refresh fa-fw fa-spin"></i></code> (Latest: <code>{{ Version::getDaemon() }}</code>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>System Information</td>
|
||||
<td data-attr="info-system"><i class="fa fa-refresh fa-fw fa-spin"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total CPU Cores</td>
|
||||
<td data-attr="info-cpus"><i class="fa fa-refresh fa-fw fa-spin"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total Servers</td>
|
||||
<td>{{ count($node->servers) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Memory Allocated</td>
|
||||
<td><strong class="{{ ($stats->memory < ($node->memory * .8)) ? 'text-success' : 'text-danger' }}">{{ is_numeric($stats->memory) ? $stats->memory : 0 }} MB</strong> of
|
||||
@if(!is_null($node->memory_overallocate))
|
||||
<abbr data-toggle="tooltip" data-placement="top" title="Allows up to {{ ($node->memory * (1 + ($node->memory_overallocate / 100)) - $node->memory) }} MB over">{{ $node->memory }}</abbr>
|
||||
@else
|
||||
{{ $node->memory }}
|
||||
@endif
|
||||
MB
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Disk Allocated</td>
|
||||
<td><strong class="{{ ($stats->disk < ($node->disk * .8)) ? 'text-success' : 'text-danger' }}">{{ is_numeric($stats->disk) ? $stats->disk : 0 }} MB</strong> of
|
||||
@if(!is_null($node->disk_overallocate))
|
||||
<abbr data-toggle="tooltip" data-placement="top" title="Allows up to {{ ($node->disk * (1 + ($node->disk_overallocate / 100)) - $node->disk) }} MB over">{{ $node->disk }}</abbr>
|
||||
@else
|
||||
{{ $node->disk }}
|
||||
@endif
|
||||
MB
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="panel-heading" style="border-top: 1px solid #ddd;"></div>
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<canvas id="chart_memory" style="max-height:300px;"></canvas>
|
||||
<canvas id="chart_cpu" style="max-height:300px;"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane" id="tab_settings">
|
||||
<form method="POST" action="/admin/nodes/view/{{ $node->id }}">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"></div>
|
||||
<div class="panel-body">
|
||||
<div class="alert alert-warning">
|
||||
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 <span class="label label-warning"><i class="fa fa-power-off"></i></span> below.
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
<label for="name" class="control-label">Node Name</label>
|
||||
<div>
|
||||
<input type="text" autocomplete="off" name="name" class="form-control" value="{{ old('name', $node->name) }}" />
|
||||
<p class="text-muted"><small>Character limits: <code>a-zA-Z0-9_.-</code> and <code>[Space]</code> (min 1, max 100 characters).</small></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-4">
|
||||
<label for="name" class="control-label">Location</label>
|
||||
<div>
|
||||
<select name="location_id" class="form-control">
|
||||
@foreach($locations as $location)
|
||||
<option value="{{ $location->id }}" {{ (old('location_id', $node->location) === $location->id) ? 'selected' : '' }}>{{ $location->long }} ({{ $location->short }})</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-2">
|
||||
<label for="public" class="control-label">Public <sup><a data-toggle="tooltip" data-placement="top" title="Allow automatic allocation to this Node?">?</a></sup></label>
|
||||
<div>
|
||||
<input type="radio" name="public" value="1" {{ (old('public', $node->public) === '1') ? 'checked' : '' }} id="public_1" checked> <label for="public_1" style="padding-left:5px;">Yes</label><br />
|
||||
<input type="radio" name="public" value="0" {{ (old('public', $node->public) === '0') ? 'checked' : '' }} id="public_0"> <label for="public_0" style="padding-left:5px;">No</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
<label for="fqdn" class="control-label">Fully Qualified Domain Name</label>
|
||||
<div>
|
||||
<input type="text" autocomplete="off" name="fqdn" class="form-control" value="{{ old('fqdn', $node->fqdn) }}" />
|
||||
</div>
|
||||
<p class="text-muted"><small>Please enter domain name (e.g <code>node.example.com</code>) to be used for connecting to the daemon. An IP address may only be used if you are not using SSL for this node.
|
||||
<a tabindex="0" data-toggle="popover" data-trigger="focus" title="Why do I need a FQDN?" data-content="In order to secure communications between your server and this node we use SSL. We cannot generate a SSL certificate for IP Addresses, and as such you will need to provide a FQDN.">Why?</a>
|
||||
</small></p>
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<label for="scheme" class="control-label"><span class="label label-warning"><i class="fa fa-power-off"></i></span> Secure Socket Layer</label>
|
||||
<div class="row" style="padding: 7px 0;">
|
||||
<div class="col-xs-6">
|
||||
<input type="radio" name="scheme" value="https" id="scheme_ssl" {{ (old('scheme', $node->scheme) === 'https') ? 'checked' : '' }}/> <label for="scheme_ssl" style="padding-left: 5px;">Enable HTTPS/SSL</label>
|
||||
</div>
|
||||
<div class="col-xs-6">
|
||||
<input type="radio" name="scheme" value="http" id="scheme_nossl" {{ (old('scheme', $node->scheme) === 'http') ? 'checked' : '' }}/> <label for="scheme_nossl" style="padding-left: 5px;">Disable HTTPS/SSL</label>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-muted"><small>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.</small></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-heading" style="border-top: 1px solid #ddd;"></div>
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="form-group col-md-3 col-xs-6">
|
||||
<label for="memory" class="control-label">Total Memory</label>
|
||||
<div class="input-group">
|
||||
<input type="text" name="memory" class="form-control" data-multiplicator="true" value="{{ old('memory', $node->memory) }}"/>
|
||||
<span class="input-group-addon">MB</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-3 col-xs-6">
|
||||
<label for="memory_overallocate" class="control-label">Overallocate</label>
|
||||
<div class="input-group">
|
||||
<input type="text" name="memory_overallocate" class="form-control" value="{{ old('memory_overallocate', $node->memory_overallocate) }}"/>
|
||||
<span class="input-group-addon">%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-3 col-xs-6">
|
||||
<label for="disk" class="control-label">Disk Space</label>
|
||||
<div class="input-group">
|
||||
<input type="text" name="disk" class="form-control" data-multiplicator="true" value="{{ old('disk', $node->disk) }}"/>
|
||||
<span class="input-group-addon">MB</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-3 col-xs-6">
|
||||
<label for="disk_overallocate" class="control-label">Overallocate</label>
|
||||
<div class="input-group">
|
||||
<input type="text" name="disk_overallocate" class="form-control" value="{{ old('disk_overallocate', $node->disk_overallocate) }}"/>
|
||||
<span class="input-group-addon">%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p class="text-muted"><small>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 <code>-1</code> into the field. Entering <code>0</code> will prevent creating new servers if it would put the node over the limit.</small></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-heading" style="border-top: 1px solid #ddd;"></div>
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
<label for="disk_overallocate" class="control-label">Maximum Web Upload Filesize</label>
|
||||
<div class="input-group">
|
||||
<input type="text" name="upload_size" class="form-control" value="{{ old('upload_size', $node->upload_size) }}"/>
|
||||
<span class="input-group-addon">MB</span>
|
||||
</div>
|
||||
<p class="text-muted"><small>Enter the maximum size of files that can be uploaded through the web-based file manager.</small></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-heading" style="border-top: 1px solid #ddd;"></div>
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="col-xs-6">
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
<label for="daemonListen" class="control-label"><span class="label label-warning"><i class="fa fa-power-off"></i></span> Daemon Port</label>
|
||||
<div>
|
||||
<input type="text" name="daemonListen" class="form-control" value="{{ old('daemonListen', $node->daemonListen) }}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<label for="daemonSFTP" class="control-label"><span class="label label-warning"><i class="fa fa-power-off"></i></span> Daemon SFTP Port</label>
|
||||
<div>
|
||||
<input type="text" name="daemonSFTP" class="form-control" value="{{ old('daemonSFTP', $node->daemonSFTP) }}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p class="text-muted"><small>The daemon runs its own SFTP management container and does not use the SSHd process on the main physical server. <Strong>Do not use the same port that you have assigned for your physcial server's SSH process.</strong></small></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-6">
|
||||
<div class="row">
|
||||
<div class="form-group col-md-12">
|
||||
<label for="reset_secret" class="control-label">Reset Daemon Key</label>
|
||||
<div style="padding: 7px 0;">
|
||||
<input type="checkbox" name="reset_secret" id="reset_secret" /> Reset Daemon Master Key
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<p class="text-muted"><small>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.</small></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-heading" style="border-top: 1px solid #ddd;"></div>
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
{!! csrf_field() !!}
|
||||
<input type="submit" class="btn btn-sm btn-primary" value="Update Node Information" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="tab-pane" id="tab_configuration">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"></div>
|
||||
<div class="panel-body">
|
||||
<div class="col-md-8">
|
||||
<p class="text-muted small">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.</p>
|
||||
</div>
|
||||
<div class="col-md-4 text-center">
|
||||
<p><button type="button" id="configTokenBtn" class="btn btn-sm btn-primary" style="width:100%;">Generate Token</button></p>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<pre><code>{{ $node->getConfigurationAsJson(true) }}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane" id="tab_allocation">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"></div>
|
||||
<div class="panel-body">
|
||||
<h4 style="margin-top:0;">Allocate Additional Ports</h4>
|
||||
<form action="{{ route('admin.nodes.post.allocations', $node->id) }}" method="POST">
|
||||
<div class="row" id="duplicate">
|
||||
<div class="col-md-4 fuelux">
|
||||
<label for="" class="control-label">IP Address or FQDN</label>
|
||||
<div class="input-group input-append dropdown combobox allocationComboBox" data-initialize="combobox">
|
||||
<input type="text" name="allocate_ip[]" class="form-control pillbox_ip" style="border-right:0;">
|
||||
<div class="input-group-btn">
|
||||
<button type="button" class="btn btn-sm btn-primary dropdown-toggle" data-toggle="dropdown"><span class="caret"></span></button>
|
||||
<ul class="dropdown-menu dropdown-menu-right">
|
||||
@foreach($node->allocations->unique('ip')->values()->all() as $allocation)
|
||||
<li data-action="alloc_dropdown_val" data-value="{{ $allocation->ip }}"><a href="#">{{ $allocation->ip }}</a></li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-7 col-xs-10 fuelux">
|
||||
<label for="" class="control-label">Ports</label>
|
||||
<div class="pillbox allocationPillbox" data-initialize="pillbox">
|
||||
<ul class="clearfix pill-group">
|
||||
<li class="pillbox-input-wrap btn-group">
|
||||
<input type="text" class="form-control dropdown-toggle pillbox-add-item" placeholder="add port">
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p class="text-muted"><small>You <strong>must</strong> enter a comma (<code>,</code>) or press the enter key after each port or range that you enter. They should appear in a blue box.</small></p>
|
||||
<input name="allocate_port[]" type="hidden" class="pillboxMain"/>
|
||||
</div>
|
||||
<div class="form-group col-md-1 col-xs-2" style="margin-left: -10px;">
|
||||
<label for="" class="control-label"> </label>
|
||||
<button class="btn btn-danger btn-allocate-delete removeClone disabled"><i class="fa fa-close"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<hr />
|
||||
{!! csrf_field() !!}
|
||||
<input type="submit" class="btn btn-sm btn-primary" value="Add Ports" />
|
||||
<button class="btn btn-success btn-sm cloneElement">Add More Rows</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="panel-heading" style="border-top: 1px solid #ddd;"></div>
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<table class="table table-hover" style="margin-bottom:0;">
|
||||
<thead style="font-weight:bold;">
|
||||
<td>IP Address <i class="fa fa-fw fa-minus-square" style="font-weight:normal;color:#d9534f;cursor:pointer;" data-toggle="modal" data-target="#allocationModal"></i></td>
|
||||
<td>IP Alias</td>
|
||||
<td>Port</td>
|
||||
<td>Assigned To</td>
|
||||
<td></td>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($node->allocations as $allocation)
|
||||
<tr>
|
||||
<td class="col-sm-3 align-middle">{{ $allocation->ip }}</td>
|
||||
<td class="col-sm-3 align-middle">
|
||||
<input class="form-control input-sm" type="text" value="{{ $allocation->ip_alias }}" data-action="set-alias" data-id="{{ $allocation->id }}" placeholder="none" />
|
||||
<span class="input-loader"><i class="fa fa-refresh fa-spin fa-fw"></i></span>
|
||||
</td>
|
||||
<td class="col-sm-2 align-middle">{{ $allocation->port }}</td>
|
||||
<td class="col-sm-3 align-middle">
|
||||
@if(! is_null($allocation->server))
|
||||
<a href="{{ route('admin.servers.view', $allocation->server_id) }}">{{ $allocation->server->name }}</a>
|
||||
@endif
|
||||
</td>
|
||||
<td class="col-sm-1 align-middle">
|
||||
@if(is_null($allocation->server_id))
|
||||
<a href="#" data-action="deallocate" data-id="{{ $allocation->id }}"><span class="badge label-danger"><i class="fa fa-trash-o"></i></span></a>
|
||||
@else
|
||||
<span class="badge label-default"><i class="fa fa-trash-o"></i></span>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="col-md-12 text-center">
|
||||
{{ $node->allocations->appends(['tab' => 'tab_allocation'])->render() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane" id="tab_servers">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"></div>
|
||||
<div class="panel-body">
|
||||
<div class="alert alert-info">
|
||||
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 <code>10%</code> and the CPU usage below displays <code>90%</code> that means the server is using <code>9%</code> of the total system CPU.
|
||||
</div>
|
||||
<table class="table table-striped" style="margin-bottom: 0;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Owner</th>
|
||||
<th>Service</th>
|
||||
<th class="text-center">Memory</th>
|
||||
<th class="text-center">Disk</th>
|
||||
<th class="text-center">CPU</th>
|
||||
<th class="text-center">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($node->servers as $server)
|
||||
<tr data-server="{{ $server->uuid }}">
|
||||
<td><a href="/admin/servers/view/{{ $server->id }}">{{ $server->name }}</a></td>
|
||||
<td><a href="/admin/users/view/{{ $server->owner_id }}"><code>{{ $server->user->email }}</a></a></td>
|
||||
<td>{{ $server->service->name }}</td>
|
||||
<td class="text-center"><span data-action="memory">--</span> / {{ $server->memory === 0 ? '∞' : $server->memory }} MB</td>
|
||||
<td class="text-center">{{ $server->disk }} MB</td>
|
||||
<td class="text-center"><span data-action="cpu" data-cpumax="{{ $server->cpu }}">--</span> %</td>
|
||||
<td class="text-center" data-action="status">--</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if(count($node->servers) === 0)
|
||||
<div class="tab-pane" id="tab_delete">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"></div>
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="col-xs-4 text-center">
|
||||
<form action="{{ route('admin.nodes.delete', $node->id) }}" method="POST" id="deleteNodeForm">
|
||||
{!! method_field('DELETE') !!}
|
||||
{!! csrf_field() !!}
|
||||
<input type="submit" value="Delete Node" class="btn btn-sm btn-danger" />
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-xs-8">
|
||||
<div class="alert alert-danger" style="margin-bottom:0;">Deleting this node is a permanent action, it cannot be undone.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-11" id="col11_setter"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" id="allocationModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title">Delete Allocations for IP Block</h4>
|
||||
</div>
|
||||
<form action="{{ route('admin.nodes.view', $node->id) }}/deallocate/block" method="POST">
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<select class="form-control" name="ip">
|
||||
@foreach($node->allocations->unique('ip')->values()->all() as $allocation)
|
||||
<option value="{{ $allocation->ip }}">{{ $allocation->ip }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
{{{ csrf_field() }}}
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
<button type="submit" class="btn btn-danger">Delete Allocations</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('#sidebar_links').find("a[href='/admin/nodes']").addClass('active');
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
$('[data-toggle="popover"]').popover({
|
||||
placement: 'auto'
|
||||
});
|
||||
|
||||
$('#deleteNodeForm').submit(function (event) {
|
||||
event.preventDefault();
|
||||
swal({
|
||||
type: 'warning',
|
||||
title: 'Are You Sure?',
|
||||
text: 'This will immediately delete this node, there is no undo.',
|
||||
showCancelButton: true,
|
||||
allowOutsideClick: true,
|
||||
confirmButtonText: 'Delete',
|
||||
confirmButtonColor: '#d9534f',
|
||||
}, function () {
|
||||
event.target.submit();
|
||||
});
|
||||
});
|
||||
|
||||
$('#configTokenBtn').on('click', function (event) {
|
||||
$.getJSON('{{ route('admin.nodes.configuration-token', $node->id) }}')
|
||||
.done(function (data) {
|
||||
swal({
|
||||
type: 'success',
|
||||
title: 'Token created.',
|
||||
text: 'Your token will expire at ' + data.expires_at + '<br /><br />' +
|
||||
'<p>To auto-configure your node run<br /><small><pre>npm run configure -- --panel-url {{ config('app.url') }} --token '+data.token+'</pre></small></p>',
|
||||
html: true
|
||||
})
|
||||
})
|
||||
.fail(function () {
|
||||
swal({
|
||||
title: 'Error',
|
||||
text: 'Something went wrong creating your token.',
|
||||
type: 'error'
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
$('.cloneElement').on('click', function (event) {
|
||||
event.preventDefault();
|
||||
var rnd = randomKey(10);
|
||||
var cloned = $('#duplicate').clone().attr('id', rnd);
|
||||
cloned.find('.allocationPillbox').removeClass('allocationPillbox').addClass('allocationPillbox_' + rnd);
|
||||
cloned.find('.pillboxMain').removeClass('pillboxMain').addClass('pillbox_' + rnd);
|
||||
cloned.find('.removeClone').removeClass('disabled').attr('data-parent', rnd);
|
||||
cloned.find('.pillbox_ip').removeClass('pillbox_ip').addClass('pillbox_ip_' + rnd);
|
||||
cloned.insertAfter('#duplicate');
|
||||
$('.allocationPillbox_' + rnd).pillbox();
|
||||
$('.allocationPillbox_' + rnd).on('added.fu.pillbox edited.fu.pillbox removed.fu.pillbox', function pillboxChanged() {
|
||||
$('.pillbox_' + rnd).val(JSON.stringify($('.allocationPillbox_' + rnd).pillbox('items')));
|
||||
});
|
||||
$('.removeClone').unbind().on('click', function (event) {
|
||||
event.preventDefault();
|
||||
var element = $(this);
|
||||
$('#' + element.attr('data-parent')).slideUp(function () {
|
||||
element.remove();
|
||||
$('.pillbox_' + element.attr('data-parent')).remove();
|
||||
$('.pillbox_ip_' + element.attr('data-parent')).remove();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$('.allocationPillbox').pillbox();
|
||||
$('.allocationComboBox').combobox();
|
||||
$('.allocationPillbox').on('added.fu.pillbox edited.fu.pillbox removed.fu.pillbox', function pillboxChanged() {
|
||||
$('.pillboxMain').val(JSON.stringify($('.allocationPillbox').pillbox('items')));
|
||||
});
|
||||
|
||||
var notifySocketError = false;
|
||||
var Status = {
|
||||
0: 'Off',
|
||||
1: 'On',
|
||||
2: 'Starting',
|
||||
3: 'Stopping'
|
||||
};
|
||||
|
||||
// Main Socket Object
|
||||
var socket = io('{{ $node->scheme }}://{{ $node->fqdn }}:{{ $node->daemonListen }}/stats/', {
|
||||
'query': 'token={{ $node->daemonSecret }}'
|
||||
});
|
||||
|
||||
// Socket Failed to Connect
|
||||
socket.io.on('connect_error', function (err) {
|
||||
if(typeof notifySocketError !== 'object') {
|
||||
notifySocketError = $.notify({
|
||||
message: '{!! trans('server.ajax.socket_error') !!}'
|
||||
}, {
|
||||
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.');
|
||||
});
|
||||
|
||||
var ctc = $('#chart_cpu');
|
||||
var timeLabels = [];
|
||||
var cpuData = [];
|
||||
var CPUChart = new Chart(ctc, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: timeLabels,
|
||||
datasets: [
|
||||
{
|
||||
label: "Percent Use",
|
||||
fill: false,
|
||||
lineTension: 0.03,
|
||||
backgroundColor: "#00A1CB",
|
||||
borderColor: "#00A1CB",
|
||||
borderCapStyle: 'butt',
|
||||
borderDash: [],
|
||||
borderDashOffset: 0.0,
|
||||
borderJoinStyle: 'miter',
|
||||
pointBorderColor: "rgba(75,192,192,1)",
|
||||
pointBackgroundColor: "#fff",
|
||||
pointBorderWidth: 1,
|
||||
pointHoverRadius: 5,
|
||||
pointHoverBackgroundColor: "rgba(75,192,192,1)",
|
||||
pointHoverBorderColor: "rgba(220,220,220,1)",
|
||||
pointHoverBorderWidth: 2,
|
||||
pointRadius: 1,
|
||||
pointHitRadius: 10,
|
||||
data: cpuData,
|
||||
spanGaps: false,
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
title: {
|
||||
display: true,
|
||||
text: 'CPU Usage (as Percent Total)'
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
animation: {
|
||||
duration: 1,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var ctm = $('#chart_memory');
|
||||
var memoryData = [];
|
||||
var MemoryChart = new Chart(ctm, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: timeLabels,
|
||||
datasets: [
|
||||
{
|
||||
label: "Memory Use",
|
||||
fill: false,
|
||||
lineTension: 0.03,
|
||||
backgroundColor: "#01A4A4",
|
||||
borderColor: "#01A4A4",
|
||||
borderCapStyle: 'butt',
|
||||
borderDash: [],
|
||||
borderDashOffset: 0.0,
|
||||
borderJoinStyle: 'miter',
|
||||
pointBorderColor: "rgba(75,192,192,1)",
|
||||
pointBackgroundColor: "#fff",
|
||||
pointBorderWidth: 1,
|
||||
pointHoverRadius: 5,
|
||||
pointHoverBackgroundColor: "rgba(75,192,192,1)",
|
||||
pointHoverBorderColor: "rgba(220,220,220,1)",
|
||||
pointHoverBorderWidth: 2,
|
||||
pointRadius: 1,
|
||||
pointHitRadius: 10,
|
||||
data: memoryData,
|
||||
spanGaps: false,
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Memory Usage (in Megabytes)'
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
animation: {
|
||||
duration: 1,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('live-stats', function (data) {
|
||||
if (cpuData.length > 10) {
|
||||
cpuData.shift();
|
||||
memoryData.shift();
|
||||
timeLabels.shift();
|
||||
}
|
||||
|
||||
cpuData.push(data.stats.cpu);
|
||||
memoryData.push(parseInt(data.stats.memory / (1024 * 1024)));
|
||||
|
||||
var m = new Date();
|
||||
timeLabels.push($.format.date(new Date(), 'HH:mm:ss'));
|
||||
|
||||
CPUChart.update();
|
||||
MemoryChart.update();
|
||||
|
||||
$.each(data.servers, function (uuid, info) {
|
||||
var element = $('tr[data-server="' + uuid + '"]');
|
||||
element.find('[data-action="status"]').html(Status[info.status]);
|
||||
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('--');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('span[data-action="delete"]').hover(function() {
|
||||
$(this).find('i').css('color', '#d9534f').removeClass('fa-square-o').addClass('fa-minus-square');
|
||||
}, function () {
|
||||
$(this).find('i').css('color', 'inherit').addClass('fa-square-o').removeClass('fa-minus-square');
|
||||
});
|
||||
|
||||
$('a[data-action="deallocate"]').click(function (event) {
|
||||
event.preventDefault();
|
||||
var element = $(this);
|
||||
var allocation = $(this).data('id');
|
||||
swal({
|
||||
title: '',
|
||||
text: 'Are you sure you want to delete this allocation?',
|
||||
type: 'warning',
|
||||
showCancelButton: true,
|
||||
allowOutsideClick: true,
|
||||
closeOnConfirm: false,
|
||||
confirmButtonText: 'Delete',
|
||||
confirmButtonColor: '#d9534f',
|
||||
showLoaderOnConfirm: true
|
||||
}, function () {
|
||||
$.ajax({
|
||||
method: 'DELETE',
|
||||
url: '{{ route('admin.nodes.view', $node->id) }}/deallocate/single/' + allocation,
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||
}
|
||||
}).done(function (data) {
|
||||
element.parent().parent().addClass('warning').delay(100).fadeOut();
|
||||
swal({
|
||||
type: 'success',
|
||||
title: 'Port Deleted!',
|
||||
});
|
||||
}).fail(function (jqXHR) {
|
||||
console.error(jqXHR);
|
||||
swal({
|
||||
title: 'Whoops!',
|
||||
text: jqXHR.responseJSON.error,
|
||||
type: 'error'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
var typingTimer;
|
||||
$('input[data-action="set-alias"]').keyup(function () {
|
||||
clearTimeout(typingTimer);
|
||||
$(this).parent().removeClass('has-error has-success');
|
||||
typingTimer = setTimeout(sendAlias, 700, $(this));
|
||||
});
|
||||
|
||||
var fadeTimers = [];
|
||||
function sendAlias(element) {
|
||||
element.parent().find('.input-loader').show();
|
||||
clearTimeout(fadeTimers[element.data('id')]);
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
url: '{{ route('admin.nodes.alias', $node->id) }}',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||
},
|
||||
data: {
|
||||
alias: element.val(),
|
||||
allocation: element.data('id')
|
||||
}
|
||||
}).done(function (data) {
|
||||
element.parent().addClass('has-success');
|
||||
}).fail(function (jqXHR) {
|
||||
console.error(jqXHR);
|
||||
element.parent().addClass('has-error');
|
||||
}).always(function () {
|
||||
element.parent().find('.input-loader').hide();
|
||||
fadeTimers[element.data('id')] = setTimeout(clearHighlight, 2500, element);
|
||||
});
|
||||
}
|
||||
|
||||
function clearHighlight(element) {
|
||||
element.parent().removeClass('has-error has-success');
|
||||
}
|
||||
|
||||
(function getInformation() {
|
||||
$.ajax({
|
||||
method: 'GET',
|
||||
url: '{{ $node->scheme }}://{{ $node->fqdn }}:{{ $node->daemonListen }}',
|
||||
timeout: 5000,
|
||||
headers: {
|
||||
'X-Access-Token': '{{ $node->daemonSecret }}'
|
||||
},
|
||||
}).done(function (data) {
|
||||
$('[data-attr="info-version"]').html(data.version);
|
||||
$('[data-attr="info-system"]').html(data.system.type + '(' + data.system.arch + ') <code>' + data.system.release + '</code>');
|
||||
$('[data-attr="info-cpus"]').html(data.system.cpus);
|
||||
}).fail(function (jqXHR) {
|
||||
|
||||
}).always(function() {
|
||||
setTimeout(getInformation, 10000);
|
||||
});
|
||||
})();
|
||||
});
|
||||
</script>
|
||||
@endsection
|
|
@ -1,83 +0,0 @@
|
|||
{{-- Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com> --}}
|
||||
|
||||
{{-- 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')
|
||||
<div class="col-md-12">
|
||||
<ul class="breadcrumb">
|
||||
<li><a href="/admin">Admin Control</a></li>
|
||||
<li class="active">Servers</li>
|
||||
</ul>
|
||||
<h3>All Servers</h3><hr />
|
||||
<form method="GET" style="margin-bottom:20px;">
|
||||
<div class="input-group">
|
||||
<input type="text" name="filter" class="form-control" value="{{ urldecode(request()->filter) }}" placeholder="search term" />
|
||||
<div class="input-group-btn">
|
||||
<button type="submit" class="btn btn-sm btn-primary">Filter Servers</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Server Name</th>
|
||||
<th>Owner</th>
|
||||
<th>Node</th>
|
||||
<th class="hidden-xs">SFTP Username</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($servers as $server)
|
||||
<tr
|
||||
@if($server->suspended === 1 && !$server->trashed())
|
||||
class="warning"
|
||||
@elseif($server->trashed())
|
||||
class="danger"
|
||||
@endif
|
||||
data-server="{{ $server->uuidShort }}">
|
||||
<td>
|
||||
<a href="/admin/servers/view/{{ $server->id }}">{{ $server->name }}</a>
|
||||
@if($server->suspended === 1 && !$server->trashed())
|
||||
<span class="label label-warning">Suspended</span>
|
||||
@elseif($server->trashed())
|
||||
<span class="label label-danger">Pending Deletion</span>
|
||||
@endif
|
||||
</td>
|
||||
<td><a href="/admin/users/view/{{ $server->user->id }}">{{ $server->user->email }}</a></td>
|
||||
<td><a href="/admin/nodes/view/{{ $server->node->id }}">{{ $server->node->name }}</a></td>
|
||||
<td class="hidden-xs"><code>{{ $server->username }}</code></td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-center">{!! $servers->render() !!}</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('#sidebar_links').find("a[href='/admin/servers']").addClass('active');
|
||||
});
|
||||
</script>
|
||||
@endsection
|
|
@ -1,511 +0,0 @@
|
|||
{{-- Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com> --}}
|
||||
|
||||
{{-- 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')
|
||||
<div class="col-md-12">
|
||||
<ul class="breadcrumb">
|
||||
<li><a href="/admin">Admin Control</a></li>
|
||||
<li><a href="/admin/servers">Servers</a></li>
|
||||
<li class="active">Create New Server</li>
|
||||
</ul>
|
||||
<h3>Create New Server</h3><hr />
|
||||
<form action="/admin/servers/new" method="POST">
|
||||
<div class="well">
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
<label for="name" class="control-label">Server Name</label>
|
||||
<div>
|
||||
<input type="text" autocomplete="off" name="name" class="form-control" value="{{ old('name') }}" />
|
||||
<p class="text-muted"><small><em>Character limits: <code>a-z A-Z 0-9 _ - .</code> and <code>[Space]</code> (max 200 characters).</em></small></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<label for="owner" class="control-label">Owner Email</label>
|
||||
<div>
|
||||
{{-- Hacky workaround to prevent Safari and Chrome from trying to suggest emails here --}}
|
||||
<input id="fake_user_name" name="fake_user[name]" style="position:absolute; top:-10000px;" tabindex="5" type="text" value="Autofill Me">
|
||||
<input type="text" autocomplete="off" name="owner" class="form-control" value="{{ old('owner', Input::get('email')) }}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="load_settings">
|
||||
<div class="well">
|
||||
<div class="row">
|
||||
<div class="ajax_loading_box" style="display:none;"><i class="fa fa-refresh fa-spin ajax_loading_position"></i></div>
|
||||
<div class="form-group col-md-6">
|
||||
<label for="location" class="control-label">Server Location</label>
|
||||
<div>
|
||||
<select name="location_id" id="getLocation" class="form-control">
|
||||
<option disabled selected> -- Select a Location</option>
|
||||
@foreach($locations as $location)
|
||||
<option value="{{ $location->id }}">{{ $location->long }} ({{ $location->short }})</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<p class="text-muted"><small>The location in which this server will be deployed.</small></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-6 hidden" id="allocationNode">
|
||||
<label for="node" class="control-label">Server Node</label>
|
||||
<div>
|
||||
<select name="node_id" id="getNode" class="form-control">
|
||||
<option disabled selected> -- Select a Node</option>
|
||||
</select>
|
||||
<p class="text-muted"><small>The node which this server will be deployed to.</small></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6 hidden" id="allocationIP">
|
||||
<label for="ip" class="control-label">Server IP</label>
|
||||
<div>
|
||||
<select name="ip" id="getIP" class="form-control">
|
||||
<option disabled selected> -- Select an IP</option>
|
||||
</select>
|
||||
<p class="text-muted"><small>Select the main IP that this server will be listening on. You can assign additional open IPs and ports below.</small></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-6 hidden" id="allocationPort">
|
||||
<label for="port" class="control-label">Server Port</label>
|
||||
<div>
|
||||
<select name="port" id="getPort" class="form-control"></select>
|
||||
<p class="text-muted"><small>Select the main port that this server will be listening on.</small></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 fuelux">
|
||||
<hr style="margin-top: 10px;"/>
|
||||
<div class="checkbox highlight" style="margin: 0;">
|
||||
<label class="checkbox-custom highlight" data-initialize="checkbox">
|
||||
<input class="sr-only" name="auto_deploy" type="checkbox" @if(isset($oldInput['auto_deploy']))checked="checked"@endif value="1"> <strong>Enable Automatic Deployment</strong>
|
||||
<p class="text-muted"><small>Check this box if you want the panel to automatically select a node and allocation for this server in the given location.</small><p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="well">
|
||||
<div class="row">
|
||||
<div class="form-group col-md-4 col-xs-4">
|
||||
<label for="memory" class="control-label">Memory</label>
|
||||
<div class="input-group">
|
||||
<input type="text" name="memory" data-multiplicator="true" class="form-control" value="{{ old('memory') }}"/>
|
||||
<span class="input-group-addon">MB</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-4 col-xs-4">
|
||||
<label for="memory" class="control-label">Swap</label>
|
||||
<div class="input-group">
|
||||
<input type="text" name="swap" data-multiplicator="true" class="form-control" value="{{ old('swap', 0) }}"/>
|
||||
<span class="input-group-addon">MB</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-4 col-xs-4">
|
||||
<label for="memory" class="control-label">OOM Killer</label>
|
||||
<div>
|
||||
<span class="input-group-addon" style="height:36px;">
|
||||
<input type="checkbox" name="oom_disabled"/>
|
||||
</span>
|
||||
<span class="input-group-addon" style="height:36px;">
|
||||
Disable OOM Killer
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p class="text-muted"><small>If you do not want to assign swap space to a server simply put <code>0</code> for the value, or <code>-1</code> to allow unlimited swap space. If you want to disable memory limiting on a server simply enter <code>0</code> 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.</small><p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group col-md-4 col-xs-4">
|
||||
<label for="disk" class="control-label">Disk Space</label>
|
||||
<div class="input-group">
|
||||
<input type="text" name="disk" data-multiplicator="true" class="form-control" value="{{ old('disk') }}"/>
|
||||
<span class="input-group-addon">MB</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-4 col-xs-4">
|
||||
<label for="cpu" class="control-label">CPU Limit</label>
|
||||
<div class="input-group">
|
||||
<input type="text" name="cpu" class="form-control" value="{{ old('cpu', 0) }}"/>
|
||||
<span class="input-group-addon">%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-4 col-xs-4">
|
||||
<label for="io" class="control-label">Block I/O</label>
|
||||
<div class="input-group">
|
||||
<input type="text" name="io" class="form-control" value="{{ old('io', 500) }}"/>
|
||||
<span class="input-group-addon">I/O</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p class="text-muted"><small>If you do not want to limit CPU usage set the value to <code>0</code>. To determine a value, take the number <em>physical</em> cores and multiply it by 100. For example, on a quad core system <code>(4 * 100 = 400)</code> there is <code>400%</code> available. To limit a server to using half of a single core, you would set the value to <code>50</code>. To allow a server to use up to two physical cores, set the value to <code>200</code>. BlockIO should be a value between <code>10</code> and <code>1000</code>. Please see <a href="https://docs.docker.com/engine/reference/run/#/block-io-bandwidth-blkio-constraint" target="_blank">this documentation</a> for more information about it.</small><p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6" id="load_services">
|
||||
<div class="well">
|
||||
<div class="row">
|
||||
<div class="ajax_loading_box" style="display:none;"><i class="fa fa-refresh fa-spin ajax_loading_position"></i></div>
|
||||
<div class="form-group col-md-12">
|
||||
<label for="service_id" class="control-label">Service Type</label>
|
||||
<div>
|
||||
<select name="service_id" id="getService" class="form-control">
|
||||
<option disabled selected> -- Select a Service</option>
|
||||
@foreach($services as $service)
|
||||
<option value="{{ $service->id }}">{{ $service->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<p class="text-muted"><small>Select the type of service that this server will be running.</small></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-12 hidden">
|
||||
<label for="option_id" class="control-label">Service Option</label>
|
||||
<div>
|
||||
<select name="option_id" id="getOption" class="form-control">
|
||||
<option disabled selected> -- Select a Service Option</option>
|
||||
</select>
|
||||
<p class="text-muted"><small>Select the type of service that this server will be running.</small></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-12 hidden">
|
||||
<label for="pack_id" class="control-label">Service Pack</label>
|
||||
<div>
|
||||
<select name="pack_id" id="getPack" class="form-control">
|
||||
<option disabled selected> -- Select a Service Pack</option>
|
||||
</select>
|
||||
<p class="text-muted"><small>Select the service pack that should be used for this server. This option can be changed later.</small></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="well">
|
||||
<div class="row">
|
||||
<div class="form-group col-md-12">
|
||||
<label for="use_custom_image" class="control-label">Use Custom Docker Image</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon">
|
||||
<input @if(old('use_custom_image') === 'use_custom_image')checked="checked"@endif type="checkbox" name="use_custom_image"/>
|
||||
</span>
|
||||
<input type="text" class="form-control" name="custom_image_name" value="{{ old('custom_image_name') }}" disabled />
|
||||
</div>
|
||||
<p class="text-muted"><small>If you would like to use a custom docker image for this server please enter it here. Most users can ignore this option.</small></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="well" id="ServiceOption" style="display:none;">
|
||||
<div class="row">
|
||||
<div class="form-group col-md-12">
|
||||
<h3 class="nopad">Service Setup & Options</h3>
|
||||
<hr />
|
||||
<label for="startup" class="control-label">Startup Command</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon" id="startupExec"></span>
|
||||
<input type="text" class="form-control" name="startup" value="{{ old('startup') }}" />
|
||||
</div>
|
||||
<p class="text-muted"><small>The following data replacers are avaliable for the startup command: <code>@{{SERVER_MEMORY}}</code>, <code>@{{SERVER_IP}}</code>, and <code>@{{SERVER_PORT}}</code>. They will be replaced with the allocated memory, server ip, and server port respectively.</small></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="alert alert-info">Some service options have additional environment variables that you can define for a given instance. They will show up below when you select a service option. If none show up, chances are that none were defined, and there is nothing to worry about.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" id="ServerVariable"></div>
|
||||
</div>
|
||||
<div class="well">
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-center">
|
||||
{!! csrf_field() !!}
|
||||
<input type="submit" class="btn btn-primary btn-sm" value="Create New Server" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
|
||||
$('#sidebar_links').find("a[href='/admin/servers/new']").addClass('active');
|
||||
|
||||
$('input[name="use_custom_image"]').change(function () {
|
||||
$('input[name="custom_image_name"]').val('').prop('disabled', !($(this).is(':checked')));
|
||||
});
|
||||
|
||||
// Typeahead
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: '{{ route('admin.users.json') }}',
|
||||
}).done(function (data) {
|
||||
$('input[name="owner"]').typeahead({ fitToElement: true, source: data });
|
||||
}).fail(function (jqXHR) {
|
||||
alert('Could not initialize user email typeahead.')
|
||||
console.log(jqXHR);
|
||||
});
|
||||
|
||||
var nodeData = null;
|
||||
var currentLocation = null;
|
||||
var currentNode = null;
|
||||
var currentService = null;
|
||||
$('#getLocation').on('change', function (event) {
|
||||
|
||||
if ($('#getLocation').val() === '' || $('#getLocation').val() === currentLocation) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentLocation = $('#getLocation').val();
|
||||
currentNode = null;
|
||||
|
||||
// Hide Existing, and Reset contents
|
||||
$('#getNode').html('<option disabled selected> -- Select a Node</option>').parent().parent().addClass('hidden');
|
||||
$('#getIP').html('<option disabled selected> -- Select an IP</option>').parent().parent().addClass('hidden');
|
||||
$('#getPort').html('').parent().parent().addClass('hidden');
|
||||
|
||||
handleLoader('#load_settings', true);
|
||||
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
url: '/admin/servers/new/get-nodes',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||
},
|
||||
data: {
|
||||
location: $('#getLocation').val()
|
||||
}
|
||||
}).done(function (data) {
|
||||
//var data = $.parseJSON(data);
|
||||
$.each(data, function (i, item) {
|
||||
var isPublic = (item.public !== 1) ? '(Private Node)' : '';
|
||||
$('#getNode').append('<option value="' + item.id + '">' + item.name + ' ' + isPublic + '</option>');
|
||||
});
|
||||
$('#getNode').parent().parent().removeClass('hidden')
|
||||
}).fail(function (jqXHR) {
|
||||
alert('An error occured while attempting to load a list of nodes in this location.');
|
||||
currentLocation = null;
|
||||
console.error(jqXHR);
|
||||
}).always(function () {
|
||||
handleLoader('#load_settings');
|
||||
})
|
||||
});
|
||||
$('#getNode').on('change', function (event) {
|
||||
|
||||
if ($('#getNode').val() === '' || $('#getNode').val() === currentNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentNode = $('#getNode').val();
|
||||
|
||||
// Hide Existing, and Reset contents
|
||||
$('#getIP').html('<option disabled selected> -- Select an IP</option>').parent().parent().addClass('hidden');
|
||||
$('#getPort').html('').parent().parent().addClass('hidden');
|
||||
|
||||
handleLoader('#load_settings', true);
|
||||
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
url: '/admin/servers/new/get-ips',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||
},
|
||||
data: {
|
||||
node: $('#getNode').val()
|
||||
}
|
||||
}).done(function (data) {
|
||||
nodeData = data;
|
||||
$.each(data, function (ip, ports) {
|
||||
$('#getIP').append('<option value="' + ip + '">' + ip + '</option>');
|
||||
});
|
||||
$('#getIP').parent().parent().removeClass('hidden');
|
||||
}).fail(function (jqXHR) {
|
||||
alert('An error occured while attempting to get IPs and Ports avaliable on this node.');
|
||||
currentNode = null;
|
||||
console.error(jqXHR);
|
||||
}).always(function () {
|
||||
handleLoader('#load_settings');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
$('#getIP').on('change', function (event) {
|
||||
|
||||
if ($('#getIP').val() === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$('#getPort').html('');
|
||||
|
||||
$.each(nodeData[$('#getIP').val()], function (i, port) {
|
||||
$('#getPort').append('<option value="' + port +'">' + port + '</option>');
|
||||
});
|
||||
|
||||
$('#getPort').parent().parent().removeClass('hidden');
|
||||
|
||||
});
|
||||
|
||||
$('input[name="auto_deploy"]').change(function () {
|
||||
if ($(this).is(':checked')) {
|
||||
$('#allocationPort, #allocationIP, #allocationNode').hide();
|
||||
} else {
|
||||
currentLocation = null;
|
||||
$('#allocationPort, #allocationIP, #allocationNode').show().addClass('hidden');
|
||||
$('#getLocation').trigger('change', function (e) {
|
||||
alert('triggered');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$('#getService').on('change', function (event) {
|
||||
|
||||
if ($('#getService').val() === '' || $('#getService').val() === currentService) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentService = $('#getService').val();
|
||||
handleLoader('#load_services', true);
|
||||
$('#ServiceOption').slideUp();
|
||||
$('#getOption').html('<option disabled selected> -- Select a Service Option</option>');
|
||||
$('#getPack').html('<option disabled selected> -- Select a Service Pack</option>');
|
||||
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
url: '/admin/servers/new/service-options',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||
},
|
||||
data: {
|
||||
service: $('#getService').val()
|
||||
}
|
||||
}).done(function (data) {
|
||||
$.each(data, function (i, option) {
|
||||
$('#getOption').append('<option value="' + option.id + '" data-image="' + option.docker_image + '">' + option.name + '</option>');
|
||||
});
|
||||
$('#getOption').parent().parent().removeClass('hidden');
|
||||
}).fail(function (jqXHR) {
|
||||
alert('An error occured while attempting to list options for this service.');
|
||||
currentService = null;
|
||||
console.error(jqXHR);
|
||||
}).always(function () {
|
||||
handleLoader('#load_services');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
$('#getOption').on('change', function (event) {
|
||||
|
||||
handleLoader('#load_services', true);
|
||||
handleLoader('#ServiceOption', true);
|
||||
$('#ServerVariable').html('');
|
||||
$('input[name="custom_image_name"]').val($(this).find(':selected').data('image'));
|
||||
$('#getPack').html('<option disabled selected> -- Select a Service Pack</option>');
|
||||
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
url: '/admin/servers/new/option-details',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||
},
|
||||
data: {
|
||||
option: $('#getOption').val()
|
||||
}
|
||||
}).done(function (data) {
|
||||
$('#startupExec').html(data.exec);
|
||||
$('input[name="startup"]').val(data.startup);
|
||||
|
||||
$.each(data.packs, function (i, item) {
|
||||
$('#getPack').append('<option value="' + item.id + '">' + item.name + ' (' + item.version + ')</option>');
|
||||
});
|
||||
$('#getPack').append('<option value="0">No Service Pack</option>').parent().parent().removeClass('hidden');
|
||||
|
||||
$.each(data.variables, function (i, item) {
|
||||
var isRequired = (item.required === 1) ? '<span class="label label-primary">Required</span> ' : '';
|
||||
var dataAppend = ' \
|
||||
<div class="form-group col-md-12">\
|
||||
<label for="var_ref_' + item.id + '" class="control-label">' + isRequired + item.name + '</label> \
|
||||
<div>\
|
||||
<input type="text" autocomplete="off" name="env_' + item.env_variable + '" class="form-control" value="' + item.default_value + '" />\
|
||||
<p class="text-muted"><small>' + item.description + '</small></p>\
|
||||
<p class="text-muted"><small>Regex Requirements for Input: <code>' + item.regex + '</code></small></p>\
|
||||
<p class="text-muted"><small>Access in Startup: <code>@{{' + item.env_variable + '}}</code></small></p>\
|
||||
</div>\
|
||||
</div>\
|
||||
';
|
||||
$('#ServerVariable').append(dataAppend);
|
||||
});
|
||||
$('#ServiceOption').slideDown();
|
||||
}).fail(function (jqXHR) {
|
||||
console.error(jqXHR);
|
||||
}).always(function () {
|
||||
handleLoader('#load_services');
|
||||
handleLoader('#ServiceOption');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// Show Loading Animation
|
||||
function handleLoader (element, show) {
|
||||
|
||||
var spinner = $(element).find('.ajax_loading_position');
|
||||
var popover = $(element).find('.ajax_loading_box');
|
||||
|
||||
// Show Animation
|
||||
if (typeof show !== 'undefined') {
|
||||
var height = $(element).height();
|
||||
var width = $(element).width();
|
||||
var center_height = (height / 2) - 16;
|
||||
var center_width = (width / 2) - 16;
|
||||
spinner.css({
|
||||
'top': center_height,
|
||||
'left': center_width,
|
||||
'font-size': '32px'
|
||||
});
|
||||
popover.css({
|
||||
'height': height,
|
||||
'margin': '-20px 0 0 -5px',
|
||||
'width': width
|
||||
}).fadeIn();
|
||||
} else {
|
||||
popover.hide();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
</script>
|
||||
@endsection
|
|
@ -1,97 +0,0 @@
|
|||
{{-- Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com> --}}
|
||||
|
||||
{{-- 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')
|
||||
<div class="col-md-12">
|
||||
<ul class="breadcrumb">
|
||||
<li><a href="/admin">Admin Control</a></li>
|
||||
<li class="active">Settings</li>
|
||||
</ul>
|
||||
<h3 class="nopad">Panel Settings</h3><hr />
|
||||
<form action="{{ route('admin.settings') }}" method="POST">
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
<label class="control-label">Company Name:</label>
|
||||
<div>
|
||||
<input type="text" class="form-control" name="company" value="{{ old('company', Settings::get('company')) }}" />
|
||||
<p class="text-muted"><small>This is the name that is used throughout the panel and in emails sent to clients.</small></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<label class="control-label">Default Language:</label>
|
||||
<div>
|
||||
<select name="default_language" class="form-control">
|
||||
<option value="de" @if(Settings::get('default_language') === 'de')selected @endif>Deutsch</option>
|
||||
<option value="en" @if(Settings::get('default_language', 'en') === 'en')selected @endif>English</option>
|
||||
<option value="es" @if(Settings::get('default_language') === 'es')selected @endif>Español</option>
|
||||
<option value="fr" @if(Settings::get('default_language') === 'fr')selected @endif>Français</option>
|
||||
<option value="it" @if(Settings::get('default_language') === 'it')selected @endif>Italiano</option>
|
||||
<option value="pl" @if(Settings::get('default_language') === 'pl')selected @endif>Polski</option>
|
||||
<option value="pt" @if(Settings::get('default_language') === 'pt')selected @endif>Português</option>
|
||||
<option value="ru" @if(Settings::get('default_language') === 'ru')selected @endif>русский</option>
|
||||
<option value="se" @if(Settings::get('default_language') === 'se')selected @endif>Svenska</option>
|
||||
<option value="zh" @if(Settings::get('default_language') === 'zh')selected @endif>中国的的</option>
|
||||
</select>
|
||||
<p class="text-muted"><small>This is the default language that all clients will use unless they manually change it.</small></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="alert alert-info">In order to modify your SMTP settings for sending mail you will need to edit the <code>.env</code> file in this project's root folder.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
<label class="control-label">Send Emails From:</label>
|
||||
<div>
|
||||
<input type="text" class="form-control" name="email_from" value="{{ old('email_from', Settings::get('email_from', env('MAIL_FROM', 'you@example.com'))) }}" />
|
||||
<p class="text-muted"><small>The email address that panel emails will be sent from. Note that some SMTP services require this to match for a given API key.</small></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<label class="control-label">Email Sender Name:</label>
|
||||
<div>
|
||||
<input type="text" class="form-control" name="email_sender_name" value="{{ old('email_sender_name', Settings::get('email_sender_name', env('MAIL_FROM_NAME', 'Pterodactyl Panel'))) }}" />
|
||||
<p class="text-muted"><small>The name that emails will appear to come from.</small></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="well well-sm">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
{!! csrf_field() !!}
|
||||
<input type="submit" class="btn btn-sm btn-primary" value="Modify Settings">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('#sidebar_links').find("a[href='/admin/settings']").addClass('active');
|
||||
});
|
||||
</script>
|
||||
@endsection
|
|
@ -1,73 +0,0 @@
|
|||
{{-- Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com> --}}
|
||||
{{-- Some Modifications (c) 2015 Dylan Seidt <dylan.seidt@gmail.com> --}}
|
||||
|
||||
{{-- 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')
|
||||
<div class="col-md-12">
|
||||
<ul class="breadcrumb">
|
||||
<li><a href="/admin">Admin Control</a></li>
|
||||
<li class="active">Accounts</li>
|
||||
</ul>
|
||||
<h3>All Registered Users</h3><hr />
|
||||
<form method="GET" style="margin-bottom:20px;">
|
||||
<div class="input-group">
|
||||
<input type="text" name="filter" class="form-control" value="{{ urldecode(Input::get('filter')) }}" placeholder="search term" />
|
||||
<div class="input-group-btn">
|
||||
<button type="submit" class="btn btn-sm btn-primary">Filter Users</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<table class="table table-striped table-bordered table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</td>
|
||||
<th>Email</td>
|
||||
<th>Client Name</th>
|
||||
<th>Username</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($users as $user)
|
||||
<tr class="align-middle">
|
||||
<td><code>#{{ $user->id }}</code></td>
|
||||
<td><a href="{{ route('admin.users.view', $user->id) }}">{{ $user->email }}</a></td>
|
||||
<td>{{ $user->name_last }}, {{ $user->name_first }}</td>
|
||||
<td><code>{{ $user->username }}</code></td>
|
||||
<td class="text-center"><img src="https://www.gravatar.com/avatar/{{ md5(strtolower($user->email)) }}?s=20" class="img-circle" /></td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-center">{!! $users->render() !!}</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('#sidebar_links').find("a[href='/admin/users']").addClass('active');
|
||||
});
|
||||
</script>
|
||||
@endsection
|
|
@ -1,132 +0,0 @@
|
|||
{{-- Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com> --}}
|
||||
{{-- Some Modifications (c) 2015 Dylan Seidt <dylan.seidt@gmail.com> --}}
|
||||
|
||||
{{-- 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')
|
||||
<div class="col-md-12">
|
||||
<ul class="breadcrumb">
|
||||
<li><a href="/admin">Admin Controls</a></li>
|
||||
<li><a href="/admin/users">Accounts</a></li>
|
||||
<li class="active">Add New Account</li>
|
||||
</ul>
|
||||
<h3>Create New Account</h3><hr />
|
||||
<form action="new" method="post">
|
||||
<fieldset>
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
<label for="email" class="control-label">Email</label>
|
||||
<div>
|
||||
<input type="text" autocomplete="off" name="email" value="{{ old('email') }}" class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<label for="username" class="control-label">Username</label>
|
||||
<div>
|
||||
<input type="text" autocomplete="off" name="username" value="{{ old('username') }}" class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
<label for="name_first" class="control-label">Client First Name</label>
|
||||
<div>
|
||||
<input type="text" autocomplete="off" name="name_first" value="{{ old('name_first') }}" class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<label for="name_last" class="control-label">Client Last Name</label>
|
||||
<div>
|
||||
<input type="text" autocomplete="off" name="name_last" value="{{ old('name_last') }}" class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-12">
|
||||
<label for="root_admin" class="control-label">{{ trans('strings.root_administrator') }}</label>
|
||||
<div>
|
||||
<select name="root_admin" class="form-control">
|
||||
<option value="0">{{ trans('strings.no') }}</option>
|
||||
<option value="1">{{ trans('strings.yes') }}</option>
|
||||
</select>
|
||||
<p class="text-muted"><small>Setting this to 'Yes' gives a user full administrative access.</small></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<hr />
|
||||
<div class="alert alert-info">
|
||||
<p>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.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div id="gen_pass" class=" alert alert-success" style="display:none;margin-bottom: 10px;"></div>
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<label for="pass" class="control-label">Password</label>
|
||||
<div>
|
||||
<input type="password" name="password" class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<label for="pass_2" class="control-label">Password Again</label>
|
||||
<div>
|
||||
<input type="password" name="password_confirmation" class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div>
|
||||
{!! csrf_field() !!}
|
||||
<button class="btn btn-primary btn-sm" type="submit">Create Account</button>
|
||||
<button class="btn btn-default btn-sm" id="gen_pass_bttn" type="button">Generate Password</button>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$("#sidebar_links").find("a[href='/admin/account/new']").addClass('active');
|
||||
$("#gen_pass_bttn").click(function(e){
|
||||
e.preventDefault();
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "/password-gen/12",
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||
},
|
||||
success: function(data) {
|
||||
$("#gen_pass").html('<strong>Generated Password:</strong> ' + data).slideDown();
|
||||
$('input[name="password"], input[name="password_confirmation"]').val(data);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
});
|
||||
$(document).ready(function () {
|
||||
$('#sidebar_links').find("a[href='/admin/users/new']").addClass('active');
|
||||
});
|
||||
</script>
|
||||
@endsection
|
|
@ -212,10 +212,6 @@
|
|||
</div>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
// 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');}
|
||||
|
||||
@if (count($errors) > 0)
|
||||
@foreach ($errors->all() as $error)
|
||||
<?php preg_match('/^The\s(.*?)\sfield/', $error, $matches) ?>
|
||||
|
|
Loading…
Reference in New Issue