1
1
mirror of https://github.com/pterodactyl/panel.git synced 2024-10-27 20:32:28 +01:00

Merge pull request #350 from Pterodactyl/feature/service-changes

Merge service changes into develop
This commit is contained in:
Dane Everitt 2017-03-15 20:54:13 -04:00 committed by GitHub
commit 65f9e30164
88 changed files with 3858 additions and 4046 deletions

5
.gitignore vendored
View File

@ -2,6 +2,7 @@
*.DS_Store*
.env
.vagrant/*
.vscode/*
composer.lock
@ -12,3 +13,7 @@ Vagrantfile
node_modules
yarn.lock
node_modules
_ide_helper_models.php
_ide_helper.php

View File

@ -0,0 +1,210 @@
<?php
/**
* 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.
*/
namespace Pterodactyl\Http\Controllers\Admin;
use Log;
use Alert;
use Javascript;
use Illuminate\Http\Request;
use Pterodactyl\Models\Service;
use Pterodactyl\Models\ServiceOption;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Repositories\OptionRepository;
use Pterodactyl\Repositories\VariableRepository;
use Pterodactyl\Exceptions\DisplayValidationException;
class OptionController extends Controller
{
/**
* Handles request to view page for adding new option.
*
* @param Request $request
* @return \Illuminate\View\View
*/
public function new(Request $request)
{
$services = Service::with('options')->get();
Javascript::put(['services' => $services->keyBy('id')]);
return view('admin.services.options.new', ['services' => $services]);
}
/**
* Handles POST request to create a new option.
*
* @param Request $request
* @return \Illuminate\Response\RedirectResponse
*/
public function create(Request $request)
{
$repo = new OptionRepository;
try {
$option = $repo->create($request->intersect([
'service_id', 'name', 'description', 'tag',
'docker_image', 'startup', 'config_from', 'config_startup',
'config_logs', 'config_files', 'config_stop',
]));
Alert::success('Successfully created new service option.')->flash();
return redirect()->route('admin.services.option.view', $option->id);
} catch (DisplayValidationException $ex) {
return redirect()->route('admin.services.option.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 occurred while attempting to create this service. This error has been logged.')->flash();
}
return redirect()->route('admin.services.option.new')->withInput();
}
/**
* Handles POST request to create a new option variable.
*
* @param Request $request
* @param int $id The ID of the service option to assign this variable to.
* @return \Illuminate\Response\RedirectResponse
*/
public function createVariable(Request $request, $id)
{
$repo = new VariableRepository;
try {
$variable = $repo->create($id, $request->only([
'name', 'description', 'env_variable',
'default_value', 'options', 'rules',
]));
Alert::success('New variable successfully assigned to this service option.')->flash();
} catch (DisplayValidationException $ex) {
return redirect()->route('admin.services.option.variables', $id)->withErrors(json_decode($ex->getMessage()));
} catch (DisplayException $ex) {
Alert::danger($ex->getMessage())->flash();
} catch (\Exception $ex) {
Log::error($ex);
Alert::danger('An unhandled exception was encountered while attempting to process that request. This error has been logged.')->flash();
}
return redirect()->route('admin.services.option.variables', $id);
}
/**
* Display option overview page.
*
* @param Request $request
* @param int $id
* @return \Illuminate\View\View
*/
public function viewConfiguration(Request $request, $id)
{
return view('admin.services.options.view', ['option' => ServiceOption::findOrFail($id)]);
}
/**
* Display variable overview page for a service option.
*
* @param Request $request
* @param int $id
* @return \Illuminate\View\View
*/
public function viewVariables(Request $request, $id)
{
return view('admin.services.options.variables', ['option' => ServiceOption::with('variables')->findOrFail($id)]);
}
/**
* Handles POST when editing a configration for a service option.
*
* @param Request $request
* @return \Illuminate\Response\RedirectResponse
*/
public function editConfiguration(Request $request, $id)
{
$repo = new OptionRepository;
try {
if ($request->input('action') !== 'delete') {
$repo->update($id, $request->intersect([
'name', 'description', 'tag', 'docker_image', 'startup',
'config_from', 'config_stop', 'config_logs', 'config_files', 'config_startup',
]));
Alert::success('Service option configuration has been successfully updated.')->flash();
} else {
$option = ServiceOption::with('service')->where('id', $id)->first();
$repo->delete($id);
Alert::success('Successfully deleted service option from the system.')->flash();
return redirect()->route('admin.services.view', $option->service_id);
}
} catch (DisplayValidationException $ex) {
return redirect()->route('admin.services.option.view', $id)->withErrors(json_decode($ex->getMessage()));
} catch (DisplayException $ex) {
Alert::danger($ex->getMessage())->flash();
} catch (\Exception $ex) {
Log::error($ex);
Alert::danger('An unhandled exception occurred while attempting to perform that action. This error has been logged.')->flash();
}
return redirect()->route('admin.services.option.view', $id);
}
/**
* Handles POST when editing a configration for a service option.
*
* @param Request $request
* @param int $option
* @param int $variable
* @return \Illuminate\Response\RedirectResponse
*/
public function editVariable(Request $request, $option, $variable)
{
$repo = new VariableRepository;
try {
if ($request->input('action') !== 'delete') {
$variable = $repo->update($variable, $request->only([
'name', 'description', 'env_variable',
'default_value', 'options', 'rules',
]));
Alert::success("The service variable '{$variable->name}' has been updated.")->flash();
} else {
$repo->delete($variable);
Alert::success('That service variable has been deleted.')->flash();
}
} catch (DisplayValidationException $ex) {
return redirect()->route('admin.services.option.variables', $option)->withErrors(json_decode($ex->getMessage()));
} catch (DisplayException $ex) {
Alert::danger($ex->getMessage())->flash();
} catch (\Exception $ex) {
Log::error($ex);
Alert::danger('An unhandled exception was encountered while attempting to process that request. This error has been logged.')->flash();
}
return redirect()->route('admin.services.option.variables', $option);
}
}

View File

@ -27,130 +27,166 @@ namespace Pterodactyl\Http\Controllers\Admin;
use Log;
use Alert;
use Storage;
use Pterodactyl\Models;
use Illuminate\Http\Request;
use Pterodactyl\Models\Pack;
use Pterodactyl\Models\Service;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Repositories\ServiceRepository\Pack;
use Pterodactyl\Repositories\PackRepository;
use Pterodactyl\Exceptions\DisplayValidationException;
class PackController extends Controller
{
public function __construct()
/**
* Display listing of all packs on the system.
*
* @param Request $request
* @return \Illuminate\View\View
*/
public function index(Request $request)
{
//
$packs = Pack::with('option')->withCount('servers');
if (! is_null($request->input('query'))) {
$packs->search($request->input('query'));
}
public function listAll(Request $request)
{
return view('admin.services.packs.index', ['services' => Models\Service::all()]);
return view('admin.packs.index', ['packs' => $packs->paginate(50)]);
}
public function listByOption(Request $request, $id)
/**
* Display new pack creation form.
*
* @param Request $request
* @return \Illuminate\View\View
*/
public function new(Request $request)
{
return view('admin.services.packs.byoption', [
'option' => Models\ServiceOption::with('service', 'packs')->findOrFail($id),
return view('admin.packs.new', [
'services' => Service::with('options')->get(),
]);
}
public function listByService(Request $request, $id)
/**
* Display new pack creation modal for use with template upload.
*
* @param Request $request
* @return \Illuminate\View\View
*/
public function newTemplate(Request $request)
{
return view('admin.services.packs.byservice', [
'service' => Models\Service::with('options', 'options.packs')->findOrFail($id),
]);
}
public function new(Request $request, $opt = null)
{
return view('admin.services.packs.new', [
'services' => Models\Service::with('options')->get(),
return view('admin.packs.modal', [
'services' => Service::with('options')->get(),
]);
}
/**
* Handle create pack request and route user to location.
*
* @param Request $request
* @return \Illuminate\View\View
*/
public function create(Request $request)
{
try {
$repo = new Pack;
$pack = $repo->create($request->only([
'name', 'version', 'description',
'option', 'selectable', 'visible',
'file_upload',
]));
Alert::success('Successfully created new service!')->flash();
$repo = new PackRepository;
return redirect()->route('admin.services.packs.edit', $pack->id)->withInput();
try {
if ($request->input('action') === 'from_template') {
$pack = $repo->createWithTemplate($request->intersect(['option_id', 'file_upload']));
} else {
$pack = $repo->create($request->intersect([
'name', 'description', 'version', 'option_id',
'selectable', 'visible', 'locked', 'file_upload',
]));
}
Alert::success('Pack successfully created on the system.')->flash();
return redirect()->route('admin.packs.view', $pack->id);
} catch (DisplayValidationException $ex) {
return redirect()->route('admin.services.packs.new', $request->input('option'))->withErrors(json_decode($ex->getMessage()))->withInput();
return redirect()->route('admin.packs.new')->withErrors(json_decode($ex->getMessage()))->withInput();
} catch (DisplayException $ex) {
Alert::danger($ex->getMessage())->flash();
} catch (\Exception $ex) {
Log::error($ex);
Alert::danger('An error occured while attempting to add a new service pack.')->flash();
Alert::danger('An error occured while attempting to add a new service pack. This error has been logged.')->flash();
}
return redirect()->route('admin.services.packs.new', $request->input('option'))->withInput();
return redirect()->route('admin.packs.new')->withInput();
}
public function edit(Request $request, $id)
/**
* Display pack view template to user.
*
* @param Request $request
* @param int $id
* @return \Illuminate\View\View
*/
public function view(Request $request, $id)
{
$pack = Models\ServicePack::with('option.service')->findOrFail($id);
return view('admin.services.packs.edit', [
'pack' => $pack,
'services' => Models\Service::all()->load('options'),
'files' => Storage::files('packs/' . $pack->uuid),
return view('admin.packs.view', [
'pack' => Pack::with('servers.node', 'servers.user')->findOrFail($id),
'services' => Service::with('options')->get(),
]);
}
/**
* Handle updating or deleting pack information.
*
* @param Request $request
* @param int $id
* @return \Illuminate\Response\RedirectResponse
*/
public function update(Request $request, $id)
{
if (! is_null($request->input('action_delete'))) {
try {
$repo = new Pack;
$repo->delete($id);
Alert::success('The requested service pack has been deleted from the system.')->flash();
$repo = new PackRepository;
return redirect()->route('admin.services.packs');
try {
if ($request->input('action') !== 'delete') {
$pack = $repo->update($id, $request->intersect([
'name', 'description', 'version',
'option_id', 'selectable', 'visible', 'locked',
]));
Alert::success('Pack successfully updated.')->flash();
} else {
$repo->delete($id);
Alert::success('Pack was successfully deleted from the system.')->flash();
return redirect()->route('admin.packs');
}
} catch (DisplayValidationException $ex) {
return redirect()->route('admin.packs.view', $id)->withErrors(json_decode($ex->getMessage()));
} catch (DisplayException $ex) {
Alert::danger($ex->getMessage())->flash();
} catch (\Exception $ex) {
Log::error($ex);
Alert::danger('An error occured while attempting to delete this pack.')->flash();
Alert::danger('An error occured while attempting to edit this service pack. This error has been logged.')->flash();
}
return redirect()->route('admin.services.packs.edit', $id);
} else {
try {
$repo = new Pack;
$repo->update($id, $request->only([
'name', 'version', 'description',
'option', 'selectable', 'visible',
]));
Alert::success('Service pack has been successfully updated.')->flash();
} catch (DisplayValidationException $ex) {
return redirect()->route('admin.services.packs.edit', $id)->withErrors(json_decode($ex->getMessage()))->withInput();
} catch (\Exception $ex) {
Log::error($ex);
Alert::danger('An error occured while attempting to add edit this pack.')->flash();
}
return redirect()->route('admin.services.packs.edit', $id);
}
return redirect()->route('admin.packs.view', $id);
}
/**
* Creates an archive of the pack and downloads it to the browser.
*
* @param Request $request
* @param int $id
* @param bool $files
* @return \Illuminate\Response\BinaryFileResponse
*/
public function export(Request $request, $id, $files = false)
{
$pack = Models\ServicePack::findOrFail($id);
$pack = Pack::findOrFail($id);
$json = [
'name' => $pack->name,
'version' => $pack->version,
'description' => $pack->dscription,
'selectable' => (bool) $pack->selectable,
'visible' => (bool) $pack->visible,
'description' => $pack->description,
'selectable' => $pack->selectable,
'visible' => $pack->visible,
'locked' => $pack->locked,
];
$filename = tempnam(sys_get_temp_dir(), 'pterodactyl_');
if ((bool) $files) {
if ($files === 'with-files') {
$zip = new \ZipArchive;
if (! $zip->open($filename, \ZipArchive::CREATE)) {
abort(503, 'Unable to open file for writing.');
@ -175,31 +211,4 @@ class PackController extends Controller
])->deleteFileAfterSend(true);
}
}
public function uploadForm(Request $request, $for = null)
{
return view('admin.services.packs.upload', [
'services' => Models\Service::all()->load('options'),
]);
}
public function postUpload(Request $request)
{
try {
$repo = new Pack;
$pack = $repo->createWithTemplate($request->only(['option', 'file_upload']));
Alert::success('Successfully created new service!')->flash();
return redirect()->route('admin.services.packs.edit', $pack->id)->withInput();
} catch (DisplayValidationException $ex) {
return redirect()->back()->withErrors(json_decode($ex->getMessage()))->withInput();
} catch (DisplayException $ex) {
Alert::danger($ex->getMessage())->flash();
} catch (\Exception $ex) {
Log::error($ex);
Alert::danger('An error occured while attempting to add a new service pack.')->flash();
}
return redirect()->back();
}
}

View File

@ -493,6 +493,8 @@ class ServersController extends Controller
$repo->updateStartup($id, $request->except('_token'), true);
Alert::success('Startup variables were successfully modified and assigned for this server.')->flash();
} catch (DisplayValidationException $ex) {
return redirect()->route('admin.servers.view.startup', $id)->withErrors(json_decode($ex->getMessage()));
} catch (DisplayException $ex) {
Alert::danger($ex->getMessage())->flash();
} catch (TransferException $ex) {

View File

@ -26,7 +26,6 @@ namespace Pterodactyl\Http\Controllers\Admin;
use Log;
use Alert;
use Storage;
use Pterodactyl\Models;
use Illuminate\Http\Request;
use Pterodactyl\Exceptions\DisplayException;
@ -36,276 +35,118 @@ use Pterodactyl\Exceptions\DisplayValidationException;
class ServiceController extends Controller
{
public function __construct()
{
//
}
public function getIndex(Request $request)
/**
* Display service overview page.
*
* @param Request $request
* @return \Illuminate\View\View
*/
public function index(Request $request)
{
return view('admin.services.index', [
'services' => Models\Service::withCount('servers')->get(),
'services' => Models\Service::withCount('servers', 'options', 'packs')->get(),
]);
}
public function getNew(Request $request)
/**
* Display create service page.
*
* @param Request $request
* @return \Illuminate\View\View
*/
public function new(Request $request)
{
return view('admin.services.new');
}
public function postNew(Request $request)
/**
* Return base view for a service.
*
* @param Request $request
* @param int $id
* @return \Illuminate\View\View
*/
public function view(Request $request, $id)
{
return view('admin.services.view', [
'service' => Models\Service::with('options', 'options.servers')->findOrFail($id),
]);
}
/**
* Return function editing view for a service.
*
* @param Request $request
* @param int $id
* @return \Illuminate\View\View
*/
public function viewFunctions(Request $request, $id)
{
return view('admin.services.functions', ['service' => Models\Service::findOrFail($id)]);
}
/**
* Handle post action for new service.
*
* @param Request $request
* @return \Illuminate\Response\RedirectResponse
*/
public function create(Request $request)
{
$repo = new ServiceRepository;
try {
$repo = new ServiceRepository\Service;
$service = $repo->create($request->only([
'name', 'description', 'file',
'executable', 'startup',
$service = $repo->create($request->intersect([
'name', 'description', 'folder', 'startup',
]));
Alert::success('Successfully created new service!')->flash();
return redirect()->route('admin.services.service', $service->id);
return redirect()->route('admin.services.view', $service->id);
} catch (DisplayValidationException $ex) {
return redirect()->route('admin.services.new')->withErrors(json_decode($ex->getMessage()))->withInput();
} catch (DisplayException $ex) {
Alert::danger($ex->getMessage())->flash();
} catch (\Exception $ex) {
Log::error($ex);
Alert::danger('An error occured while attempting to add a new service.')->flash();
Alert::danger('An error occured while attempting to add a new service. This error has been logged.')->flash();
}
return redirect()->route('admin.services.new')->withInput();
}
public function getService(Request $request, $service)
/**
* Edits configuration for a specific service.
*
* @param Request $request
* @param int $id
* @return \Illuminate\Response\RedirectResponse
*/
public function edit(Request $request, $id)
{
return view('admin.services.view', [
'service' => Models\Service::with('options', 'options.servers')->findOrFail($service),
]);
}
$repo = new ServiceRepository;
$redirectTo = ($request->input('redirect_to')) ? 'admin.services.view.functions' : 'admin.services.view';
public function postService(Request $request, $service)
{
try {
$repo = new ServiceRepository\Service;
$repo->update($service, $request->only([
'name', 'description', 'file',
'executable', 'startup',
if ($request->input('action') !== 'delete') {
$repo->update($id, $request->intersect([
'name', 'description', 'folder', 'startup', 'index_file',
]));
Alert::success('Successfully updated this service.')->flash();
} catch (DisplayValidationException $ex) {
return redirect()->route('admin.services.service', $service)->withErrors(json_decode($ex->getMessage()))->withInput();
} catch (DisplayException $ex) {
Alert::danger($ex->getMessage())->flash();
} catch (\Exception $ex) {
Log::error($ex);
Alert::danger('An error occurred while attempting to update this service.')->flash();
}
return redirect()->route('admin.services.service', $service)->withInput();
}
public function deleteService(Request $request, $service)
{
try {
$repo = new ServiceRepository\Service;
$repo->delete($service);
Alert::success('Successfully deleted that service.')->flash();
Alert::success('Service has been updated successfully.')->flash();
} else {
$repo->delete($id);
Alert::success('Successfully deleted service from the system.')->flash();
return redirect()->route('admin.services');
} catch (DisplayException $ex) {
Alert::danger($ex->getMessage())->flash();
} catch (\Exception $ex) {
Log::error($ex);
Alert::danger('An error was encountered while attempting to delete that service.')->flash();
}
return redirect()->route('admin.services.service', $service);
}
public function getOption(Request $request, $service, $option)
{
$option = Models\ServiceOption::with('service', 'variables')->findOrFail($option);
$option->setRelation('servers', $option->servers()->with('user')->paginate(25));
return view('admin.services.options.view', ['option' => $option]);
}
public function postOption(Request $request, $service, $option)
{
try {
$repo = new ServiceRepository\Option;
$repo->update($option, $request->only([
'name', 'description', 'tag',
'executable', 'docker_image', 'startup',
]));
Alert::success('Option settings successfully updated.')->flash();
} catch (DisplayValidationException $ex) {
return redirect()->route('admin.services.option', [$service, $option])->withErrors(json_decode($ex->getMessage()))->withInput();
} catch (\Exception $ex) {
Log::error($ex);
Alert::danger('An error occured while attempting to modify this option.')->flash();
}
return redirect()->route('admin.services.option', [$service, $option])->withInput();
}
public function deleteOption(Request $request, $service, $option)
{
try {
$repo = new ServiceRepository\Option;
$repo->delete($option);
Alert::success('Successfully deleted that option.')->flash();
return redirect()->route('admin.services.service', $service);
return redirect()->route($redirectTo, $id)->withErrors(json_decode($ex->getMessage()))->withInput();
} catch (DisplayException $ex) {
Alert::danger($ex->getMessage())->flash();
} catch (\Exception $ex) {
Log::error($ex);
Alert::danger('An error was encountered while attempting to delete this option.')->flash();
Alert::danger('An error occurred while attempting to update this service. This error has been logged.')->flash();
}
return redirect()->route('admin.services.option', [$service, $option]);
}
public function postOptionVariable(Request $request, $service, $option, $variable)
{
try {
$repo = new ServiceRepository\Variable;
// Because of the way old() works on the display side we prefix all of the variables with thier ID
// We need to remove that prefix here since the repo doesn't want it.
$data = [
'user_viewable' => '0',
'user_editable' => '0',
'required' => '0',
];
foreach ($request->except(['_token']) as $id => $val) {
$data[str_replace($variable . '_', '', $id)] = $val;
}
$repo->update($variable, $data);
Alert::success('Successfully updated variable.')->flash();
} catch (DisplayValidationException $ex) {
$data = [];
foreach (json_decode($ex->getMessage(), true) as $id => $val) {
$data[$variable . '_' . $id] = $val;
}
return redirect()->route('admin.services.option', [$service, $option])->withErrors((object) $data)->withInput();
} catch (DisplayException $ex) {
Alert::danger($ex->getMessage())->flash();
} catch (\Exception $ex) {
Log::error($ex);
Alert::danger('An error occurred while attempting to update this service.')->flash();
}
return redirect()->route('admin.services.option', [$service, $option])->withInput();
}
public function getNewVariable(Request $request, $service, $option)
{
return view('admin.services.options.variable', [
'option' => Models\ServiceOption::with('service')->findOrFail($option),
]);
}
public function postNewVariable(Request $request, $service, $option)
{
try {
$repo = new ServiceRepository\Variable;
$repo->create($option, $request->only([
'name', 'description', 'env_variable',
'default_value', 'user_viewable',
'user_editable', 'required', 'regex',
]));
Alert::success('Successfully added new variable to this option.')->flash();
return redirect()->route('admin.services.option', [$service, $option]);
} catch (DisplayValidationException $ex) {
return redirect()->route('admin.services.option.variable.new', [$service, $option])->withErrors(json_decode($ex->getMessage()))->withInput();
} catch (DisplayException $ex) {
Alert::danger($ex->getMessage())->flash();
} catch (\Exception $ex) {
Log::error($ex);
Alert::danger('An error occurred while attempting to add this variable.')->flash();
}
return redirect()->route('admin.services.option.variable.new', [$service, $option])->withInput();
}
public function newOption(Request $request, $service)
{
return view('admin.services.options.new', [
'service' => Models\Service::findOrFail($service),
]);
}
public function postNewOption(Request $request, $service)
{
try {
$repo = new ServiceRepository\Option;
$id = $repo->create($service, $request->except([
'_token',
]));
Alert::success('Successfully created new service option.')->flash();
return redirect()->route('admin.services.option', [$service, $id]);
} catch (DisplayValidationException $ex) {
return redirect()->route('admin.services.option.new', $service)->withErrors(json_decode($ex->getMessage()))->withInput();
} catch (\Exception $ex) {
Log::error($ex);
Alert::danger('An error occured while attempting to add this service option.')->flash();
}
return redirect()->route('admin.services.option.new', $service)->withInput();
}
public function deleteVariable(Request $request, $service, $option, $variable)
{
try {
$repo = new ServiceRepository\Variable;
$repo->delete($variable);
Alert::success('Deleted variable.')->flash();
} catch (DisplayException $ex) {
Alert::danger($ex->getMessage())->flash();
} catch (\Exception $ex) {
Log::error($ex);
Alert::danger('An error occured while attempting to delete that variable.')->flash();
}
return redirect()->route('admin.services.option', [$service, $option]);
}
public function getConfiguration(Request $request, $serviceId)
{
$service = Models\Service::findOrFail($serviceId);
return view('admin.services.config', [
'service' => $service,
'contents' => [
'json' => Storage::get('services/' . $service->file . '/main.json'),
'index' => Storage::get('services/' . $service->file . '/index.js'),
],
]);
}
public function postConfiguration(Request $request, $serviceId)
{
try {
$repo = new ServiceRepository\Service;
$repo->updateFile($serviceId, $request->only(['file', 'contents']));
return response('', 204);
} catch (DisplayException $ex) {
return response()->json([
'error' => $ex->getMessage(),
], 503);
} catch (\Exception $ex) {
Log::error($ex);
return response()->json([
'error' => 'An error occured while attempting to save the file.',
], 503);
}
return redirect()->route($redirectTo, $id);
}
}

View File

@ -47,7 +47,7 @@ class PackController extends Controller
*/
public function pull(Request $request, $uuid)
{
$pack = Models\ServicePack::where('uuid', $uuid)->first();
$pack = Models\Pack::where('uuid', $uuid)->first();
if (! $pack) {
return response()->json(['error' => 'No such pack.'], 404);
@ -68,7 +68,7 @@ class PackController extends Controller
*/
public function hash(Request $request, $uuid)
{
$pack = Models\ServicePack::where('uuid', $uuid)->first();
$pack = Models\Pack::where('uuid', $uuid)->first();
if (! $pack) {
return response()->json(['error' => 'No such pack.'], 404);

View File

@ -24,36 +24,28 @@
namespace Pterodactyl\Http\Controllers\Daemon;
use Storage;
use Pterodactyl\Models;
use Illuminate\Http\Request;
use Pterodactyl\Models\Service;
use Pterodactyl\Models\ServiceOption;
use Pterodactyl\Http\Controllers\Controller;
class ServiceController extends Controller
{
/**
* Controller Constructor.
*/
public function __construct()
{
//
}
/**
* Returns a listing of all services currently on the system,
* as well as the associated files and the file hashes for
* caching purposes.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
* @return \Illuminate\Http\JsonResponse
*/
public function list(Request $request)
{
$response = [];
foreach (Models\Service::all() as &$service) {
$response[$service->file] = [
'main.json' => sha1_file(storage_path('app/services/' . $service->file . '/main.json')),
'index.js' => sha1_file(storage_path('app/services/' . $service->file . '/index.js')),
foreach (Service::all() as $service) {
$response[$service->folder] = [
'main.json' => sha1($this->getConfiguration($service->id)->toJson()),
'index.js' => sha1($service->index_file),
];
}
@ -64,16 +56,45 @@ class ServiceController extends Controller
* Returns the contents of the requested file for the given service.
*
* @param \Illuminate\Http\Request $request
* @param string $service
* @param string $folder
* @param string $file
* @return \Illuminate\Http\Response
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\FileResponse
*/
public function pull(Request $request, $service, $file)
public function pull(Request $request, $folder, $file)
{
if (! Storage::exists('services/' . $service . '/' . $file)) {
return response()->json(['error' => 'No such file.'], 404);
$service = Service::where('folder', $folder)->firstOrFail();
if ($file === 'index.js') {
return response($service->index_file)->header('Content-Type', 'text/plain');
} elseif ($file === 'main.json') {
return response()->json($this->getConfiguration($service->id));
}
return response()->file(storage_path('app/services/' . $service . '/' . $file));
return abort(404);
}
/**
* Returns a `main.json` file based on the configuration
* of each service option.
*
* @param int $id
* @return \Illuminate\Support\Collection
*/
protected function getConfiguration($id)
{
$options = ServiceOption::where('service_id', $id)->get();
return $options->mapWithKeys(function ($item) use ($options) {
return [
$item->tag => array_filter([
'symlink' => $options->where('id', $item->config_from)->pluck('tag')->pop(),
'startup' => json_decode($item->config_startup),
'stop' => $item->config_stop,
'configs' => json_decode($item->config_files),
'log' => json_decode($item->config_logs),
'query' => 'none',
]),
];
});
}
}

View File

@ -98,10 +98,6 @@ class RemoteController extends Controller
], 403);
}
// Passes Validation, Setup Notifications
$notify = new NotificationService($server);
$notify->pass($request->input('notification'));
return response('', 201);
}

View File

@ -24,7 +24,6 @@
namespace Pterodactyl\Http\Controllers\Server;
use DB;
use Log;
use Uuid;
use Alert;
@ -209,37 +208,20 @@ class ServerController extends Controller
public function getStartup(Request $request, $uuid)
{
$server = Models\Server::byUuid($uuid);
$server->load(['allocations' => function ($query) use ($server) {
$query->where('id', $server->allocation_id);
}]);
$server->load(['node', 'allocation', 'variables.variable']);
$this->authorize('view-startup', $server);
$variables = Models\ServiceVariable::select(
'service_variables.*',
DB::raw('COALESCE(server_variables.variable_value, service_variables.default_value) as a_serverValue')
)->leftJoin('server_variables', 'server_variables.variable_id', '=', 'service_variables.id')
->where('service_variables.option_id', $server->option_id)
->where('server_variables.server_id', $server->id)
->get();
$service = Models\Service::select(
DB::raw('IFNULL(service_options.executable, services.executable) as executable')
)->leftJoin('service_options', 'service_options.service_id', '=', 'services.id')
->where('service_options.id', $server->option_id)
->where('services.id', $server->service_id)
->first();
$allocation = $server->allocations->pop();
$ServerVariable = [
$replacements = [
'{{SERVER_MEMORY}}' => $server->memory,
'{{SERVER_IP}}' => $allocation->ip,
'{{SERVER_PORT}}' => $allocation->port,
'{{SERVER_IP}}' => $server->allocation->ip,
'{{SERVER_PORT}}' => $server->allocation->port,
];
$processed = str_replace(array_keys($ServerVariable), array_values($ServerVariable), $server->startup);
foreach ($variables as &$variable) {
$replace = ($variable->user_viewable === 1) ? $variable->a_serverValue : '[hidden]';
$processed = str_replace('{{' . $variable->env_variable . '}}', $replace, $processed);
$processed = str_replace(array_keys($replacements), array_values($replacements), $server->startup);
foreach ($server->variables as $v) {
$replace = ($v->user_can_view) ? $v->variable_value : '[hidden]';
$processed = str_replace('{{' . $v->variable->env_variable . '}}', $replace, $processed);
}
$server->js();
@ -247,8 +229,8 @@ class ServerController extends Controller
return view('server.settings.startup', [
'server' => $server,
'node' => $server->node,
'variables' => $variables->where('user_viewable', 1),
'service' => $service,
'variables' => $server->variables->where('user_can_view', true),
'service' => $server->service,
'processedStartup' => $processed,
]);
}
@ -311,6 +293,8 @@ class ServerController extends Controller
$repo = new ServerRepository;
$repo->updateStartup($server->id, $request->except('_token'));
Alert::success('Server startup variables were successfully updated.')->flash();
} catch (DisplayValidationException $ex) {
return redirect()->route('server.settings.startup', $uuid)->withErrors(json_decode($ex->getMessage()));
} catch (DisplayException $ex) {
Alert::danger($ex->getMessage())->flash();
} catch (\Exception $ex) {

View File

@ -386,126 +386,99 @@ class AdminRoutes
], function () use ($router) {
$router->get('/', [
'as' => 'admin.services',
'uses' => 'Admin\ServiceController@getIndex',
'uses' => 'Admin\ServiceController@index',
]);
$router->get('/new', [
'as' => 'admin.services.new',
'uses' => 'Admin\ServiceController@getNew',
'uses' => 'Admin\ServiceController@new',
]);
$router->post('/new', [
'uses' => 'Admin\ServiceController@postNew',
'uses' => 'Admin\ServiceController@create',
]);
$router->get('/service/{id}', [
'as' => 'admin.services.service',
'uses' => 'Admin\ServiceController@getService',
$router->get('/view/{id}', [
'as' => 'admin.services.view',
'uses' => 'Admin\ServiceController@view',
]);
$router->post('/service/{id}', [
'uses' => 'Admin\ServiceController@postService',
$router->post('/view/{id}', 'Admin\ServiceController@edit');
$router->get('/view/{id}/functions', [
'as' => 'admin.services.view.functions',
'uses' => 'Admin\ServiceController@viewFunctions',
]);
$router->delete('/service/{id}', [
'uses' => 'Admin\ServiceController@deleteService',
$router->delete('/view/{id}', [
'uses' => 'Admin\ServiceController@delete',
]);
$router->get('/service/{id}/configuration', [
'as' => 'admin.services.service.config',
'uses' => 'Admin\ServiceController@getConfiguration',
]);
$router->post('/service/{id}/configuration', [
'uses' => 'Admin\ServiceController@postConfiguration',
]);
$router->get('/service/{service}/option/new', [
// ---------------------
// Service Option Routes
// ---------------------
$router->get('/option/new', [
'as' => 'admin.services.option.new',
'uses' => 'Admin\ServiceController@newOption',
'uses' => 'Admin\OptionController@new',
]);
$router->post('/service/{service}/option/new', [
'uses' => 'Admin\ServiceController@postNewOption',
$router->post('/option/new', 'Admin\OptionController@create');
$router->get('/option/{id}', [
'as' => 'admin.services.option.view',
'uses' => 'Admin\OptionController@viewConfiguration',
]);
$router->get('/service/{service}/option/{option}', [
'as' => 'admin.services.option',
'uses' => 'Admin\ServiceController@getOption',
$router->post('/option/{id}', 'Admin\OptionController@editConfiguration');
$router->get('/option/{id}/variables', [
'as' => 'admin.services.option.variables',
'uses' => 'Admin\OptionController@viewVariables',
]);
$router->post('/service/{service}/option/{option}', [
'uses' => 'Admin\ServiceController@postOption',
]);
$router->post('/option/{id}/variables', 'Admin\OptionController@createVariable');
$router->delete('/service/{service}/option/{id}', [
'uses' => 'Admin\ServiceController@deleteOption',
]);
$router->get('/service/{service}/option/{option}/variable/new', [
'as' => 'admin.services.option.variable.new',
'uses' => 'Admin\ServiceController@getNewVariable',
]);
$router->post('/service/{service}/option/{option}/variable/new', [
'uses' => 'Admin\ServiceController@postNewVariable',
]);
$router->post('/service/{service}/option/{option}/variable/{variable}', [
'as' => 'admin.services.option.variable',
'uses' => 'Admin\ServiceController@postOptionVariable',
]);
$router->get('/service/{service}/option/{option}/variable/{variable}/delete', [
'as' => 'admin.services.option.variable.delete',
'uses' => 'Admin\ServiceController@deleteVariable',
$router->post('/option/{id}/variables/{variable}', [
'as' => 'admin.services.option.variables.edit',
'uses' => 'Admin\OptionController@editVariable',
]);
});
// Service Packs
$router->group([
'prefix' => 'admin/services/packs',
'prefix' => 'admin/packs',
'middleware' => [
'auth',
'admin',
'csrf',
],
], function () use ($router) {
$router->get('/new/{option?}', [
'as' => 'admin.services.packs.new',
$router->get('/', [
'as' => 'admin.packs',
'uses' => 'Admin\PackController@index',
]);
$router->get('/new', [
'as' => 'admin.packs.new',
'uses' => 'Admin\PackController@new',
]);
$router->post('/new', [
'uses' => 'Admin\PackController@create',
$router->post('/new', 'Admin\PackController@create');
$router->get('/new/template', [
'as' => 'admin.packs.new.template',
'uses' => 'Admin\PackController@newTemplate',
]);
$router->get('/upload/{option?}', [
'as' => 'admin.services.packs.uploadForm',
'uses' => 'Admin\PackController@uploadForm',
$router->get('/view/{id}', [
'as' => 'admin.packs.view',
'uses' => 'Admin\PackController@view',
]);
$router->post('/upload', [
'uses' => 'Admin\PackController@postUpload',
]);
$router->get('/', [
'as' => 'admin.services.packs',
'uses' => 'Admin\PackController@listAll',
]);
$router->get('/for/option/{option}', [
'as' => 'admin.services.packs.option',
'uses' => 'Admin\PackController@listByOption',
]);
$router->get('/for/service/{service}', [
'as' => 'admin.services.packs.service',
'uses' => 'Admin\PackController@listByService',
]);
$router->get('/edit/{pack}', [
'as' => 'admin.services.packs.edit',
'uses' => 'Admin\PackController@edit',
]);
$router->post('/edit/{pack}', [
'uses' => 'Admin\PackController@update',
]);
$router->get('/edit/{pack}/export/{archive?}', [
'as' => 'admin.services.packs.export',
$router->post('/view/{id}', 'Admin\PackController@update');
$router->post('/view/{id}/export/{files?}', [
'as' => 'admin.packs.view.export',
'uses' => 'Admin\PackController@export',
]);
});

125
app/Models/Pack.php Normal file
View File

@ -0,0 +1,125 @@
<?php
/**
* 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.
*/
namespace Pterodactyl\Models;
use File;
use Storage;
use Illuminate\Database\Eloquent\Model;
use Nicolaslopezj\Searchable\SearchableTrait;
class Pack extends Model
{
use SearchableTrait;
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'packs';
/**
* Fields that are mass assignable.
*
* @var array
*/
protected $fillable = [
'option_id', 'name', 'version', 'description', 'selectable', 'visible', 'locked',
];
/**
* Cast values to correct type.
*
* @var array
*/
protected $casts = [
'option_id' => 'integer',
'selectable' => 'boolean',
'visible' => 'boolean',
'locked' => 'boolean',
];
/**
* Parameters for search querying.
*
* @var array
*/
protected $searchable = [
'columns' => [
'packs.name' => 10,
'packs.uuid' => 8,
'service_options.name' => 6,
'service_options.tag' => 5,
'service_options.docker_image' => 5,
'packs.version' => 2,
],
'joins' => [
'service_options' => ['packs.option_id', 'service_options.id'],
],
];
/**
* Returns all of the archived files for a given pack.
*
* @param bool $collection
* @return \Illuminate\Support\Collection|object
*/
public function files($collection = false)
{
$files = collect(Storage::files('packs/' . $this->uuid));
$files = $files->map(function ($item) {
$path = storage_path('app/' . $item);
return (object) [
'name' => basename($item),
'hash' => sha1_file($path),
'size' => File::humanReadableSize($path),
];
});
return ($collection) ? $files : (object) $files->all();
}
/**
* Gets option associated with a service pack.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function option()
{
return $this->belongsTo(ServiceOption::class);
}
/**
* Gets servers associated with a pack.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function servers()
{
return $this->hasMany(Server::class);
}
}

View File

@ -87,17 +87,24 @@ class Server extends Model
'installed' => 'integer',
];
/**
* Parameters for search querying.
*
* @var array
*/
protected $searchable = [
'columns' => [
'servers.name' => 10,
'servers.username' => 10,
'servers.uuidShort' => 9,
'servers.uuid' => 8,
'packs.name' => 7,
'users.email' => 6,
'users.username' => 6,
'nodes.name' => 2,
],
'joins' => [
'packs' => ['packs.id', 'servers.pack_id'],
'users' => ['users.id', 'servers.owner_id'],
'nodes' => ['nodes.id', 'servers.node_id'],
],
@ -111,7 +118,7 @@ class Server extends Model
* @param string $uuid The Short-UUID of the server to return an object about.
* @return \Illuminate\Database\Eloquent\Collection
*/
public static function byUuid($uuid)
public static function byUuid($uuid, array $with = [], array $withCount = [])
{
if (! Auth::check()) {
throw new \Exception('You must call Server:byUuid as an authenticated user.');
@ -236,11 +243,11 @@ class Server extends Model
/**
* Gets information for the pack associated with this server.
*
* @return \Illuminate\Database\Eloquent\Relations\HasOne
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function pack()
{
return $this->hasOne(ServicePack::class, 'id', 'pack_id');
return $this->belongsTo(Pack::class);
}
/**

View File

@ -52,6 +52,36 @@ class ServerVariable extends Model
'variable_id' => 'integer',
];
/**
* Determine if variable is viewable by users.
*
* @return bool
*/
public function getUserCanViewAttribute()
{
return (bool) $this->variable->user_viewable;
}
/**
* Determine if variable is editable by users.
*
* @return bool
*/
public function getUserCanEditAttribute()
{
return (bool) $this->variable->user_editable;
}
/**
* Determine if variable is required.
*
* @return bool
*/
public function getRequiredAttribute()
{
return $this->variable->required;
}
/**
* Returns information about a given variables parent.
*

View File

@ -41,9 +41,52 @@ class Service extends Model
* @var array
*/
protected $fillable = [
'name', 'description', 'file', 'executable', 'startup',
'name', 'description', 'folder', 'startup', 'index_file',
];
/**
* Returns the default contents of the index.js file for a service.
*
* @return string
*/
public static function defaultIndexFile()
{
return <<<'EOF'
'use strict';
/**
* Pterodactyl - Daemon
* 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.
*/
const rfr = require('rfr');
const _ = require('lodash');
const Core = rfr('src/services/index.js');
class Service extends Core {}
module.exports = Service;
EOF;
}
/**
* Gets all service options associated with this service.
*
@ -62,7 +105,7 @@ class Service extends Model
public function packs()
{
return $this->hasManyThrough(
'Pterodactyl\Models\ServicePack', 'Pterodactyl\Models\ServiceOption',
'Pterodactyl\Models\Pack', 'Pterodactyl\Models\ServiceOption',
'service_id', 'option_id'
);
}

View File

@ -51,17 +51,6 @@ class ServiceOption extends Model
'service_id' => 'integer',
];
/**
* Returns the display executable for the option and will use the parent
* service one if the option does not have one defined.
*
* @return string
*/
public function getDisplayExecutableAttribute($value)
{
return (is_null($this->executable)) ? $this->service->executable : $this->executable;
}
/**
* Returns the display startup string for the option and will use the parent
* service one if the option does not have one defined.
@ -110,6 +99,6 @@ class ServiceOption extends Model
*/
public function packs()
{
return $this->hasMany(ServicePack::class, 'option_id');
return $this->hasMany(Pack::class, 'option_id');
}
}

View File

@ -51,9 +51,24 @@ class ServiceVariable extends Model
'option_id' => 'integer',
'user_viewable' => 'integer',
'user_editable' => 'integer',
'required' => 'integer',
];
/**
* Returns the display executable for the option and will use the parent
* service one if the option does not have one defined.
*
* @return string
*/
public function getRequiredAttribute($value)
{
return $this->rules === 'required' || str_contains($this->rules, ['required|', '|required']);
}
/**
* Return server variables associated with this variable.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function serverVariable()
{
return $this->hasMany(ServerVariable::class, 'variable_id');

View File

@ -66,7 +66,7 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
/**
* A list of mass-assignable variables.
*
* @var [type]
* @var array
*/
protected $fillable = ['username', 'email', 'name_first', 'name_last', 'password', 'language', 'use_totp', 'totp_secret', 'gravatar', 'root_admin'];
@ -88,6 +88,11 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
*/
protected $hidden = ['password', 'remember_token', 'totp_secret'];
/**
* Parameters for search querying.
*
* @var array
*/
protected $searchable = [
'columns' => [
'email' => 10,

View File

@ -49,6 +49,8 @@ class AppServiceProvider extends ServiceProvider
*/
public function register()
{
//
if ($this->app->environment() !== 'production') {
$this->app->register(\Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class);
}
}
}

View File

@ -1,8 +1,7 @@
'use strict';
<?php
/**
* Pterodactyl - Daemon
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>
* 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
@ -22,17 +21,32 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
const rfr = require('rfr');
const _ = require('lodash');
const Core = rfr('src/services/index.js');
namespace Pterodactyl\Providers;
class Service extends Core {
onConsole(data) {
// Hide the output spam from Bungeecord getting pinged.
if (_.endsWith(data, '<-> InitialHandler has connected')) return;
return super.onConsole(data);
}
use File;
use Illuminate\Support\ServiceProvider;
class MacroServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* @return void
*/
public function boot()
{
File::macro('humanReadableSize', function ($path, $precision = 2) {
$size = File::size($path);
static $units = ['B', 'kB', 'MB', 'GB', 'TB'];
$i = 0;
while (($size / 1024) > 0.9) {
$size = $size / 1024;
$i++;
}
module.exports = Service;
return round($size, ($i < 2) ? 0 : $precision) . ' ' . $units[$i];
});
}
}

View File

@ -199,7 +199,7 @@ class DatabaseRepository
* Deletes a database server from the system if it is empty.
*
* @param int $server The ID of the Database Server.
* @return
* @return bool
*/
public function delete($server)
{

View File

@ -0,0 +1,157 @@
<?php
/**
* 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.
*/
namespace Pterodactyl\Repositories;
use DB;
use Validator;
use Pterodactyl\Models\ServiceOption;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Exceptions\DisplayValidationException;
class OptionRepository
{
/**
* Creates a new service option on the system.
*
* @param array $data
* @return \Pterodactyl\Models\ServiceOption
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\DisplayValidationException
*/
public function create(array $data)
{
$validator = Validator::make($data, [
'service_id' => 'required|numeric|exists:services,id',
'name' => 'required|string|max:255',
'description' => 'required|string',
'tag' => 'required|string|max:255|unique:service_options,tag',
'docker_image' => 'required|string|max:255',
'startup' => 'required|string',
'config_from' => 'sometimes|required|numeric|exists:service_options,id',
'config_startup' => 'required_without:config_from|json',
'config_stop' => 'required_without:config_from|string|max:255',
'config_logs' => 'required_without:config_from|json',
'config_files' => 'required_without:config_from|json',
]);
if ($validator->fails()) {
throw new DisplayValidationException($validator->errors());
}
if (isset($data['config_from'])) {
if (! ServiceOption::where('service_id', $data['service_id'])->where('id', $data['config_from'])->first()) {
throw new DisplayException('The `configuration from` directive must be a child of the assigned service.');
}
}
return ServiceOption::create($data);
}
/**
* Deletes a service option from the system.
*
* @param int $id
* @return void
*
* @throws \Pterodactyl\Exceptions\DisplayException
*/
public function delete($id)
{
$option = ServiceOption::with('variables')->withCount('servers')->findOrFail($id);
if ($option->servers_count > 0) {
throw new DisplayException('You cannot delete a service option that has servers associated with it.');
}
DB::transaction(function () use ($option) {
foreach ($option->variables as $variable) {
(new VariableRepository)->delete($variable->id);
}
$option->delete();
});
}
/**
* Updates a service option in the database which can then be used
* on nodes.
*
* @param int $id
* @param array $data
* @return \Pterodactyl\Models\ServiceOption
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\DisplayValidationException
*/
public function update($id, array $data)
{
$option = ServiceOption::findOrFail($id);
// Due to code limitations (at least when I am writing this currently)
// we have to make an assumption that if config_from is not passed
// that we should be telling it that no config is wanted anymore.
//
// This really is only an issue if we open API access to this function,
// in which case users will always need to pass `config_from` in order
// to keep it assigned.
if (! isset($data['config_from']) && ! is_null($option->config_from)) {
$option->config_from = null;
}
$validator = Validator::make($data, [
'name' => 'sometimes|required|string|max:255',
'description' => 'sometimes|required|string',
'tag' => 'sometimes|required|string|max:255|unique:service_options,tag,' . $option->id,
'docker_image' => 'sometimes|required|string|max:255',
'startup' => 'sometimes|required|string',
'config_from' => 'sometimes|required|numeric|exists:service_options,id',
]);
$validator->sometimes([
'config_startup', 'config_logs', 'config_files',
], 'required_without:config_from|json', function ($input) use ($option) {
return ! (! $input->config_from && ! is_null($option->config_from));
});
$validator->sometimes('config_stop', 'required_without:config_from|string|max:255', function ($input) use ($option) {
return ! (! $input->config_from && ! is_null($option->config_from));
});
if ($validator->fails()) {
throw new DisplayValidationException($validator->errors());
}
if (isset($data['config_from'])) {
if (! ServiceOption::where('service_id', $option->service_id)->where('id', $data['config_from'])->first()) {
throw new DisplayException('The `configuration from` directive must be a child of the assigned service.');
}
}
$option->fill($data)->save();
return $option;
}
}

View File

@ -22,37 +22,42 @@
* SOFTWARE.
*/
namespace Pterodactyl\Repositories\ServiceRepository;
namespace Pterodactyl\Repositories;
use DB;
use Uuid;
use Storage;
use Validator;
use Pterodactyl\Models;
use Pterodactyl\Models\Pack;
use Pterodactyl\Services\UuidService;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Exceptions\DisplayValidationException;
class Pack
class PackRepository
{
public function __construct()
{
//
}
/**
* Creates a new pack on the system.
*
* @param array $data
* @return \Pterodactyl\Models\Pack
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\DisplayValidationException
*/
public function create(array $data)
{
$validator = Validator::make($data, [
'name' => 'required|string',
'version' => 'required|string',
'description' => 'sometimes|nullable|string',
'option' => 'required|exists:service_options,id',
'selectable' => 'sometimes|boolean',
'visible' => 'sometimes|boolean',
'selectable' => 'sometimes|required|boolean',
'visible' => 'sometimes|required|boolean',
'locked' => 'sometimes|required|boolean',
'option_id' => 'required|exists:service_options,id',
]);
if ($validator->fails()) {
throw new DisplayValidationException($validator->errors());
throw new DisplayValidationException(json_encode($validator->errors()));
}
if (isset($data['file_upload'])) {
@ -65,33 +70,42 @@ class Pack
}
}
DB::beginTransaction();
try {
$uuid = new UuidService;
$pack = Models\ServicePack::create([
'option_id' => $data['option'],
'uuid' => $uuid->generate('service_packs', 'uuid'),
return DB::transaction(function () use ($data) {
$uuid = new UuidService();
$pack = new Pack;
$pack->uuid = $uuid->generate('packs', 'uuid');
$pack->fill([
'option_id' => $data['option_id'],
'name' => $data['name'],
'version' => $data['version'],
'description' => (empty($data['description'])) ? null : $data['description'],
'selectable' => isset($data['selectable']),
'visible' => isset($data['visible']),
]);
'locked' => isset($data['locked']),
])->save();
if (! $pack->exists) {
throw new DisplayException('Model does not exist after creation. Did an event prevent it from saving?');
}
Storage::makeDirectory('packs/' . $pack->uuid);
if (isset($data['file_upload'])) {
$data['file_upload']->storeAs('packs/' . $pack->uuid, 'archive.tar.gz');
}
DB::commit();
} catch (\Exception $ex) {
DB::rollBack();
throw $ex;
}
return $pack;
});
}
/**
* Creates a new pack on the system given a template file.
*
* @param array $data
* @return \Pterodactyl\Models\Pack
*
* @throws \Pterodactyl\Exceptions\DisplayException
*/
public function createWithTemplate(array $data)
{
if (! isset($data['file_upload'])) {
@ -127,9 +141,10 @@ class Pack
'name' => $json->name,
'version' => $json->version,
'description' => $json->description,
'option' => $data['option'],
'option_id' => $data['option_id'],
'selectable' => $json->selectable,
'visible' => $json->visible,
'locked' => $json->locked,
]);
if (! $zip->extractTo(storage_path('app/packs/' . $pack->uuid), 'archive.tar.gz')) {
@ -147,42 +162,75 @@ class Pack
'name' => $json->name,
'version' => $json->version,
'description' => $json->description,
'option' => $data['option'],
'option_id' => $data['option_id'],
'selectable' => $json->selectable,
'visible' => $json->visible,
'locked' => $json->locked,
]);
}
}
/**
* Updates a pack on the system.
*
* @param int $id
* @param array $data
* @return \Pterodactyl\Models\Pack
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\DisplayValidationException
*/
public function update($id, array $data)
{
$validator = Validator::make($data, [
'name' => 'required|string',
'version' => 'required|string',
'description' => 'string',
'option' => 'required|exists:service_options,id',
'selectable' => 'sometimes|boolean',
'visible' => 'sometimes|boolean',
'name' => 'sometimes|required|string',
'option_id' => 'sometimes|required|exists:service_options,id',
'version' => 'sometimes|required|string',
'description' => 'sometimes|string',
'selectable' => 'sometimes|required|boolean',
'visible' => 'sometimes|required|boolean',
'locked' => 'sometimes|required|boolean',
]);
if ($validator->fails()) {
throw new DisplayValidationException($validator->errors());
throw new DisplayValidationException(json_encode($validator->errors()));
}
Models\ServicePack::findOrFail($id)->update([
'option_id' => $data['option'],
'name' => $data['name'],
'version' => $data['version'],
$pack = Pack::withCount('servers')->findOrFail($id);
if ($pack->servers_count > 0 && (isset($data['option_id']) && (int) $data['option_id'] !== $pack->option_id)) {
throw new DisplayException('You cannot modify the associated option if servers are attached to a pack.');
}
$pack->fill([
'name' => isset($data['name']) ? $data['name'] : $pack->name,
'option_id' => isset($data['option_id']) ? $data['option_id'] : $pack->option_id,
'version' => isset($data['version']) ? $data['version'] : $pack->version,
'description' => (empty($data['description'])) ? null : $data['description'],
'selectable' => isset($data['selectable']),
'visible' => isset($data['visible']),
]);
'locked' => isset($data['locked']),
])->save();
return $pack;
}
/**
* Deletes a pack and files from the system.
*
* @param int $id
* @return void
*
* @throws \Pterodactyl\Exceptions\DisplayException
*/
public function delete($id)
{
$pack = Models\ServicePack::findOrFail($id);
// @TODO Check for linked servers; foreign key should block this.
$pack = Models\Pack::withCount('servers')->findOrFail($id);
if ($pack->servers_count > 0) {
throw new DisplayException('Cannot delete a pack from the system if servers are assocaited with it.');
}
DB::transaction(function () use ($pack) {
$pack->delete();
Storage::deleteDirectory('packs/' . $pack->uuid);

View File

@ -156,7 +156,7 @@ class ServerRepository
if (! isset($data['pack_id']) || (int) $data['pack_id'] < 1) {
$data['pack_id'] = null;
} else {
$pack = Models\ServicePack::where('id', $data['pack_id'])->where('option_id', $data['option_id'])->first();
$pack = Models\Pack::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.');
}
@ -627,23 +627,25 @@ class ServerRepository
foreach ($server->option->variables as &$variable) {
$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;
}
// 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 . ').');
}
// Perform Field Validation
$validator = Validator::make([
'variable_value' => ($set) ? $data['env_' . $variable->id] : null,
], [
'variable_value' => $variable->rules,
]);
if ($validator->fails()) {
throw new DisplayValidationException(json_encode(
collect([
'notice' => ['There was a validation error with the `' . $variable->name . '` variable.'],
])->merge($validator->errors()->toArray())
));
}
$svar = Models\ServerVariable::firstOrNew([

View File

@ -0,0 +1,129 @@
<?php
/**
* 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.
*/
namespace Pterodactyl\Repositories;
use DB;
use Validator;
use Pterodactyl\Models\Service;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Exceptions\DisplayValidationException;
class ServiceRepository
{
/**
* Creates a new service on the system.
*
* @param array $data
* @return \Pterodactyl\Models\Service
*/
public function create(array $data)
{
$validator = Validator::make($data, [
'name' => 'required|string|min:1|max:255',
'description' => 'required|nullable|string',
'folder' => 'required|unique:services,folder|regex:/^[\w.-]{1,50}$/',
'startup' => 'required|nullable|string',
]);
if ($validator->fails()) {
throw new DisplayValidationException($validator->errors());
}
return DB::transaction(function () use ($data) {
$service = new Service;
$service->author = config('pterodactyl.service.author');
$service->fill([
'name' => $data['name'],
'description' => (isset($data['description'])) ? $data['description'] : null,
'folder' => $data['folder'],
'startup' => (isset($data['startup'])) ? $data['startup'] : null,
'index_file' => Service::defaultIndexFile(),
])->save();
// It is possible for an event to return false or throw an exception
// which won't necessarily be detected by this transaction.
//
// This check ensures the model was actually saved.
if (! $service->exists) {
throw new \Exception('Service model was created however the response appears to be invalid. Did an event fire wrongly?');
}
return $service;
});
}
/**
* Updates a service.
*
* @param int $id
* @param array $data
* @return \Pterodactyl\Models\Service
*/
public function update($id, array $data)
{
$service = Service::findOrFail($id);
$validator = Validator::make($data, [
'name' => 'sometimes|required|string|min:1|max:255',
'description' => 'sometimes|required|nullable|string',
'folder' => 'sometimes|required|regex:/^[\w.-]{1,50}$/',
'startup' => 'sometimes|required|nullable|string',
'index_file' => 'sometimes|required|string',
]);
if ($validator->fails()) {
throw new DisplayValidationException($validator->errors());
}
return DB::transaction(function () use ($data, $service) {
$service->fill($data)->save();
return $service;
});
}
/**
* Deletes a service and associated files and options.
*
* @param int $id
* @return void
*/
public function delete($id)
{
$service = Service::withCount('servers')->with('options')->findOrFail($id);
if ($service->servers_count > 0) {
throw new DisplayException('You cannot delete a service that has servers associated with it.');
}
DB::transaction(function () use ($service) {
foreach ($service->options as $option) {
(new OptionRepository)->delete($option->id);
}
$service->delete();
});
}
}

View File

@ -1,124 +0,0 @@
<?php
/**
* 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.
*/
namespace Pterodactyl\Repositories\ServiceRepository;
use DB;
use Validator;
use Pterodactyl\Models;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Exceptions\DisplayValidationException;
class Option
{
public function __construct()
{
//
}
public function create($service, array $data)
{
$service = Models\Service::findOrFail($service);
$validator = Validator::make($data, [
'name' => 'required|string|max:255',
'description' => 'required|string|min:1',
'tag' => 'required|string|max:255',
'executable' => 'sometimes|string|max:255',
'docker_image' => 'required|string|max:255',
'startup' => 'sometimes|string',
]);
if ($validator->fails()) {
throw new DisplayValidationException($validator->errors());
}
if (isset($data['executable']) && empty($data['executable'])) {
$data['executable'] = null;
}
if (isset($data['startup']) && empty($data['startup'])) {
$data['startup'] = null;
}
$option = new Models\ServiceOption;
$option->service_id = $service->id;
$option->fill($data);
$option->save();
return $option->id;
}
public function delete($id)
{
$option = Models\ServiceOption::findOrFail($id);
$servers = Models\Server::where('option', $option->id)->get();
if (count($servers) !== 0) {
throw new DisplayException('You cannot delete an option that has servers attached to it currently.');
}
DB::beginTransaction();
try {
Models\ServiceVariable::where('option_id', $option->id)->delete();
$option->delete();
DB::commit();
} catch (\Exception $ex) {
DB::rollBack();
throw $ex;
}
}
public function update($id, array $data)
{
$option = Models\ServiceOption::findOrFail($id);
$validator = Validator::make($data, [
'name' => 'sometimes|required|string|max:255',
'description' => 'sometimes|required|string|min:1',
'tag' => 'sometimes|required|string|max:255',
'executable' => 'sometimes|string|max:255',
'docker_image' => 'sometimes|required|string|max:255',
'startup' => 'sometimes|string',
]);
if ($validator->fails()) {
throw new DisplayValidationException($validator->errors());
}
if (isset($data['executable']) && empty($data['executable'])) {
$data['executable'] = null;
}
if (isset($data['startup']) && empty($data['startup'])) {
$data['startup'] = null;
}
$option->fill($data);
return $option->save();
}
}

View File

@ -1,144 +0,0 @@
<?php
/**
* 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.
*/
namespace Pterodactyl\Repositories\ServiceRepository;
use DB;
use Uuid;
use Storage;
use Validator;
use Pterodactyl\Models;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Exceptions\DisplayValidationException;
class Service
{
public function __construct()
{
//
}
public function create(array $data)
{
$validator = Validator::make($data, [
'name' => 'required|string|min:1|max:255',
'description' => 'required|string',
'file' => 'required|unique:services,file|regex:/^[\w.-]{1,50}$/',
'executable' => 'max:255|regex:/^(.*)$/',
'startup' => 'string',
]);
if ($validator->fails()) {
throw new DisplayValidationException($validator->errors());
}
DB::beginTransaction();
try {
$service = new Models\Service;
$service->author = env('SERVICE_AUTHOR', (string) Uuid::generate(4));
$service->fill($data);
$service->save();
Storage::put('services/' . $service->file . '/main.json', '{}');
Storage::copy('services/.templates/index.js', 'services/' . $service->file . '/index.js');
DB::commit();
} catch (\Exception $ex) {
DB::rollBack();
throw $ex;
}
return $service;
}
public function update($id, array $data)
{
$service = Models\Service::findOrFail($id);
$validator = Validator::make($data, [
'name' => 'sometimes|required|string|min:1|max:255',
'description' => 'sometimes|required|string',
'file' => 'sometimes|required|regex:/^[\w.-]{1,50}$/',
'executable' => 'sometimes|max:255|regex:/^(.*)$/',
'startup' => 'sometimes|string',
]);
if ($validator->fails()) {
throw new DisplayValidationException($validator->errors());
}
$service->fill($data);
return $service->save();
}
public function delete($id)
{
$service = Models\Service::findOrFail($id);
$servers = Models\Server::where('service', $service->id)->get();
$options = Models\ServiceOption::select('id')->where('service_id', $service->id);
if (count($servers) !== 0) {
throw new DisplayException('You cannot delete a service that has servers associated with it.');
}
DB::beginTransaction();
try {
Models\ServiceVariable::whereIn('option_id', $options->get()->toArray())->delete();
$options->delete();
$service->delete();
Storage::deleteDirectory('services/' . $service->file);
DB::commit();
} catch (\Exception $ex) {
DB::rollBack();
throw $ex;
}
}
public function updateFile($id, array $data)
{
$service = Models\Service::findOrFail($id);
$validator = Validator::make($data, [
'file' => 'required|in:index,main',
'contents' => 'required|string',
]);
if ($validator->fails()) {
throw new DisplayValidationException($validator->errors());
}
$filename = ($data['file'] === 'main') ? 'main.json' : 'index.js';
$filepath = 'services/' . $service->file . '/' . $filename;
$backup = 'services/.bak/' . str_random(12) . '.bak';
try {
Storage::move($filepath, $backup);
Storage::put($filepath, $data['contents']);
} catch (\Exception $ex) {
Storage::move($backup, $filepath);
throw $ex;
}
}
}

View File

@ -1,144 +0,0 @@
<?php
/**
* 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.
*/
namespace Pterodactyl\Repositories\ServiceRepository;
use DB;
use Validator;
use Pterodactyl\Models;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Exceptions\DisplayValidationException;
class Variable
{
public function __construct()
{
//
}
public function create($id, array $data)
{
$option = Models\ServiceOption::select('id')->findOrFail($id);
$validator = Validator::make($data, [
'name' => 'required|string|min:1|max:255',
'description' => 'required|string',
'env_variable' => 'required|regex:/^[\w]{1,255}$/',
'default_value' => 'string|max:255',
'user_viewable' => 'sometimes|required|nullable|boolean',
'user_editable' => 'sometimes|required|nullable|boolean',
'required' => 'sometimes|required|nullable|boolean',
'regex' => 'required|string|min:1',
]);
if ($validator->fails()) {
throw new DisplayValidationException($validator->errors());
}
if ($data['default_value'] !== '' && ! preg_match($data['regex'], $data['default_value'])) {
throw new DisplayException('The default value you entered cannot violate the regex requirements.');
}
if (Models\ServiceVariable::where('env_variable', $data['env_variable'])->where('option_id', $option->id)->first()) {
throw new DisplayException('An environment variable with that name already exists for this option.');
}
$data['user_viewable'] = (isset($data['user_viewable']) && in_array((int) $data['user_viewable'], [0, 1])) ? $data['user_viewable'] : 0;
$data['user_editable'] = (isset($data['user_editable']) && in_array((int) $data['user_editable'], [0, 1])) ? $data['user_editable'] : 0;
$data['required'] = (isset($data['required']) && in_array((int) $data['required'], [0, 1])) ? $data['required'] : 0;
$data['option_id'] = $option->id;
$variable = Models\ServiceVariable::create($data);
return $variable;
}
public function delete($id)
{
$variable = Models\ServiceVariable::with('serverVariable')->findOrFail($id);
DB::beginTransaction();
try {
foreach ($variable->serverVariable as $svar) {
$svar->delete();
}
$variable->delete();
DB::commit();
} catch (\Exception $ex) {
DB::rollBack();
throw $ex;
}
}
public function update($id, array $data)
{
$variable = Models\ServiceVariable::findOrFail($id);
$validator = Validator::make($data, [
'name' => 'sometimes|required|string|min:1|max:255',
'description' => 'sometimes|required|string',
'env_variable' => 'sometimes|required|regex:/^[\w]{1,255}$/',
'default_value' => 'sometimes|string|max:255',
'user_viewable' => 'sometimes|required|nullable|boolean',
'user_editable' => 'sometimes|required|nullable|boolean',
'required' => 'sometimes|required|nullable|boolean',
'regex' => 'sometimes|required|string|min:1',
]);
if ($validator->fails()) {
throw new DisplayValidationException($validator->errors());
}
$data['default_value'] = (isset($data['default_value'])) ? $data['default_value'] : $variable->default_value;
$data['regex'] = (isset($data['regex'])) ? $data['regex'] : $variable->regex;
if ($data['default_value'] !== '' && ! preg_match($data['regex'], $data['default_value'])) {
throw new DisplayException('The default value you entered cannot violate the regex requirements.');
}
if (Models\ServiceVariable::where('id', '!=', $variable->id)->where('env_variable', $data['env_variable'])->where('option_id', $variable->option_id)->first()) {
throw new DisplayException('An environment variable with that name already exists for this option.');
}
$data['user_viewable'] = (isset($data['user_viewable']) && in_array((int) $data['user_viewable'], [0, 1])) ? $data['user_viewable'] : $variable->user_viewable;
$data['user_editable'] = (isset($data['user_editable']) && in_array((int) $data['user_editable'], [0, 1])) ? $data['user_editable'] : $variable->user_editable;
$data['required'] = (isset($data['required']) && in_array((int) $data['required'], [0, 1])) ? $data['required'] : $variable->required;
// Not using $data because the function that passes into this function
// can't do $requst->only() due to the page setup.
$variable->fill([
'name' => $data['name'],
'description' => $data['description'],
'env_variable' => $data['env_variable'],
'default_value' => $data['default_value'],
'user_viewable' => $data['user_viewable'],
'user_editable' => $data['user_editable'],
'required' => $data['required'],
'regex' => $data['regex'],
]);
return $variable->save();
}
}

View File

@ -0,0 +1,166 @@
<?php
/**
* 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.
*/
namespace Pterodactyl\Repositories;
use DB;
use Validator;
use Pterodactyl\Models\ServiceOption;
use Pterodactyl\Models\ServiceVariable;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Exceptions\DisplayValidationException;
class VariableRepository
{
/**
* Create a new service variable.
*
* @param int $option
* @param array $data
* @return \Pterodactyl\Models\ServiceVariable
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\DisplayValidationException
*/
public function create($option, array $data)
{
$option = ServiceOption::select('id')->findOrFail($option);
$validator = Validator::make($data, [
'name' => 'required|string|min:1|max:255',
'description' => 'sometimes|nullable|string',
'env_variable' => 'required|regex:/^[\w]{1,255}$/',
'default_value' => 'string',
'options' => 'sometimes|required|array',
'rules' => 'bail|required|string|min:1',
]);
// Ensure the default value is allowed by the rules provided.
$rules = (isset($data['rules'])) ? $data['rules'] : $variable->rules;
$validator->sometimes('default_value', $rules, function ($input) {
return $input->default_value;
});
if ($validator->fails()) {
throw new DisplayValidationException($validator->errors());
}
if (isset($data['env_variable'])) {
$search = ServiceVariable::where('env_variable', $data['env_variable'])->where('option_id', $option->id);
if ($search->first()) {
throw new DisplayException('The envionment variable name assigned to this variable must be unique for this service option.');
}
}
if (! isset($data['options']) || ! is_array($data['options'])) {
$data['options'] = [];
}
$data['option_id'] = $option->id;
$data['user_viewable'] = (in_array('user_viewable', $data['options']));
$data['user_editable'] = (in_array('user_editable', $data['options']));
// Remove field that isn't used.
unset($data['options']);
return ServiceVariable::create($data);
}
/**
* Deletes a specified option variable as well as all server
* variables currently assigned.
*
* @param int $id
* @return void
*/
public function delete($id)
{
$variable = ServiceVariable::with('serverVariable')->findOrFail($id);
DB::transaction(function () use ($variable) {
foreach ($variable->serverVariable as $v) {
$v->delete();
}
$variable->delete();
});
}
/**
* Updates a given service variable.
*
* @param int $id
* @param array $data
* @return \Pterodactyl\Models\ServiceVariable
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\DisplayValidationException
*/
public function update($id, array $data)
{
$variable = ServiceVariable::findOrFail($id);
$validator = Validator::make($data, [
'name' => 'sometimes|required|string|min:1|max:255',
'description' => 'sometimes|nullable|string',
'env_variable' => 'sometimes|required|regex:/^[\w]{1,255}$/',
'default_value' => 'string',
'options' => 'sometimes|required|array',
'rules' => 'bail|sometimes|required|string|min:1',
]);
// Ensure the default value is allowed by the rules provided.
$rules = (isset($data['rules'])) ? $data['rules'] : $variable->rules;
$validator->sometimes('default_value', $rules, function ($input) {
return $input->default_value;
});
if ($validator->fails()) {
throw new DisplayValidationException($validator->errors());
}
if (isset($data['env_variable'])) {
$search = ServiceVariable::where('env_variable', $data['env_variable'])
->where('option_id', $variable->option_id)
->where('id', '!=', $variable->id);
if ($search->first()) {
throw new DisplayException('The envionment variable name assigned to this variable must be unique for this service option.');
}
}
if (! isset($data['options']) || ! is_array($data['options'])) {
$data['options'] = [];
}
$data['user_viewable'] = (in_array('user_viewable', $data['options']));
$data['user_editable'] = (in_array('user_editable', $data['options']));
// Remove field that isn't used.
unset($data['options']);
$variable->fill($data)->save();
return $variable;
}
}

View File

@ -79,7 +79,7 @@ class DeploymentService
* @param int $memory
* @param int $disk
* @param int $location
* @return \Pterodactyl\Models\Node;
* @return \Pterodactyl\Models\Node
*
* @throws \Pterodactyl\Exceptions\DisplayException
*/
@ -103,7 +103,7 @@ class DeploymentService
/**
* Returns a random allocation for a node.
* @param int $node
* @return \Models\Pterodactyl\Allocation;
* @return \Models\Pterodactyl\Allocation
*/
public static function randomAllocation($node)
{

View File

View File

@ -1,62 +0,0 @@
<?php
/**
* 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.
*/
namespace Pterodactyl\Services;
use Pterodactyl\Models\Server;
use Pterodactyl\Notifications\Daemon;
class NotificationService
{
protected $server;
protected $user;
/**
* Daemon will pass an event name, this matches that event name with the notification to send.
* @var array
*/
protected $types = [
// 'crashed' => 'CrashNotification',
// 'started' => 'StartNotification',
// 'stopped' => 'StopNotification',
// 'rebuild' => 'RebuildNotification'
];
public function __construct(Server $server)
{
$this->server = $server;
}
public function pass(array $notification)
{
if (! $notification->type) {
return;
}
if (class_exists($this->types[$notification->type]::class)) {
$user->notify(new $this->types[$notification->type]($notification->payload));
}
}
}

View File

@ -38,7 +38,8 @@
"phpunit/phpunit": "~5.0",
"symfony/css-selector": "3.1.*",
"symfony/dom-crawler": "3.1.*",
"laravel/homestead": "3.0.*"
"laravel/homestead": "3.0.*",
"barryvdh/laravel-ide-helper": "^2.3"
},
"autoload": {
"classmap": [

175
composer.lock generated
View File

@ -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": "6a9656aff0fb3809d27a2a093a810197",
"content-hash": "8affaad10f155172b5079a72015b8bc5",
"hash": "7c01d337ebab3d47ae3479360c63e68f",
"content-hash": "c2addd888e0a4fd26518ea7c691f6f25",
"packages": [
{
"name": "aws/aws-sdk-php",
@ -3747,6 +3747,121 @@
}
],
"packages-dev": [
{
"name": "barryvdh/laravel-ide-helper",
"version": "v2.3.2",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-ide-helper.git",
"reference": "e82de98cef0d6597b1b686be0b5813a3a4bb53c5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/e82de98cef0d6597b1b686be0b5813a3a4bb53c5",
"reference": "e82de98cef0d6597b1b686be0b5813a3a4bb53c5",
"shasum": ""
},
"require": {
"barryvdh/reflection-docblock": "^2.0.4",
"illuminate/console": "^5.0,<5.5",
"illuminate/filesystem": "^5.0,<5.5",
"illuminate/support": "^5.0,<5.5",
"php": ">=5.4.0",
"symfony/class-loader": "^2.3|^3.0"
},
"require-dev": {
"doctrine/dbal": "~2.3",
"phpunit/phpunit": "4.*",
"scrutinizer/ocular": "~1.1",
"squizlabs/php_codesniffer": "~2.3"
},
"suggest": {
"doctrine/dbal": "Load information from the database about models for phpdocs (~2.3)"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.3-dev"
}
},
"autoload": {
"psr-4": {
"Barryvdh\\LaravelIdeHelper\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Barry vd. Heuvel",
"email": "barryvdh@gmail.com"
}
],
"description": "Laravel IDE Helper, generates correct PHPDocs for all Facade classes, to improve auto-completion.",
"keywords": [
"autocomplete",
"codeintel",
"helper",
"ide",
"laravel",
"netbeans",
"phpdoc",
"phpstorm",
"sublime"
],
"time": "2017-02-22 12:27:33"
},
{
"name": "barryvdh/reflection-docblock",
"version": "v2.0.4",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/ReflectionDocBlock.git",
"reference": "3dcbd98b5d9384a5357266efba8fd29884458e5c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/ReflectionDocBlock/zipball/3dcbd98b5d9384a5357266efba8fd29884458e5c",
"reference": "3dcbd98b5d9384a5357266efba8fd29884458e5c",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"phpunit/phpunit": "~4.0,<4.5"
},
"suggest": {
"dflydev/markdown": "~1.0",
"erusev/parsedown": "~1.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"autoload": {
"psr-0": {
"Barryvdh": [
"src/"
]
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mike van Riel",
"email": "mike.vanriel@naenius.com"
}
],
"time": "2016-06-13 19:28:20"
},
{
"name": "doctrine/instantiator",
"version": "1.0.5",
@ -5008,6 +5123,62 @@
"homepage": "https://github.com/sebastianbergmann/version",
"time": "2016-10-03 07:35:21"
},
{
"name": "symfony/class-loader",
"version": "v3.2.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/class-loader.git",
"reference": "c29a5bc6ca14cfff1f5e3d7781ed74b6e898d2b9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/class-loader/zipball/c29a5bc6ca14cfff1f5e3d7781ed74b6e898d2b9",
"reference": "c29a5bc6ca14cfff1f5e3d7781ed74b6e898d2b9",
"shasum": ""
},
"require": {
"php": ">=5.5.9"
},
"require-dev": {
"symfony/finder": "~2.8|~3.0",
"symfony/polyfill-apcu": "~1.1"
},
"suggest": {
"symfony/polyfill-apcu": "For using ApcClassLoader on HHVM"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.2-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\ClassLoader\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony ClassLoader Component",
"homepage": "https://symfony.com",
"time": "2017-02-18 17:28:00"
},
{
"name": "symfony/css-selector",
"version": "v3.1.10",

View File

@ -150,6 +150,7 @@ return [
Pterodactyl\Providers\AuthServiceProvider::class,
Pterodactyl\Providers\EventServiceProvider::class,
Pterodactyl\Providers\RouteServiceProvider::class,
Pterodactyl\Providers\MacroServiceProvider::class,
Pterodactyl\Providers\PhraseAppTranslationProvider::class,
/*

19
config/pterodactyl.php Normal file
View File

@ -0,0 +1,19 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Service Author
|--------------------------------------------------------------------------
|
| Each panel installation is assigned a unique UUID to identify the
| author of custom services, and make upgrades easier by identifying
| standard Pterodactyl shipped services.
*/
'service' => [
'core' => 'ptrdctyl-v040-11e6-8b77-86f30ca893d3',
'author' => env('SERVICE_AUTHOR'),
],
];

View File

@ -0,0 +1,56 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class DeleteServiceExecutableOption extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
DB::transaction(function () {
Schema::table('services', function (Blueprint $table) {
$table->renameColumn('file', 'folder');
$table->text('description')->nullable()->change();
$table->text('startup')->nullable()->change();
});
// Attempt to fix any startup commands for servers
// that we possibly can.
foreach (ServiceOption::with('servers')->get() as $option) {
$option->servers->each(function ($s) use ($option) {
$prepend = $option->display_executable;
$prepend = ($prepend === './ShooterGameServer') ? './ShooterGame/Binaries/Linux/ShooterGameServer' : $prepend;
$prepend = ($prepend === 'TerrariaServer.exe') ? 'mono TerrariaServer.exe' : $prepend;
$s->startup = $prepend . ' ' . $s->startup;
$s->save();
});
}
Schema::table('services', function (Blueprint $table) {
$table->dropColumn('executable');
});
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('services', function (Blueprint $table) {
$table->string('executable')->after('folder');
$table->renameColumn('folder', 'file');
$table->text('description')->nullable(false)->change();
$table->text('startup')->nullable(false)->change();
});
}
}

View File

@ -0,0 +1,48 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddNewServiceOptionsColumns extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('service_options', function (Blueprint $table) {
$table->dropColumn('executable');
$table->unsignedInteger('config_from')->nullable()->after('docker_image');
$table->string('config_stop')->nullable()->after('docker_image');
$table->text('config_logs')->nullable()->after('docker_image');
$table->text('config_startup')->nullable()->after('docker_image');
$table->text('config_files')->nullable()->after('docker_image');
$table->foreign('config_from')->references('id')->on('service_options');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('service_options', function (Blueprint $table) {
$table->dropForeign('config_from');
$table->dropColumn('config_from');
$table->dropColumn('config_stop');
$table->dropColumn('config_logs');
$table->dropColumn('config_startup');
$table->dropColumn('config_files');
$table->string('executable')->after('docker_image')->nullable();
});
}
}

View File

@ -21,49 +21,45 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
use Pterodactyl\Models\Service;
use Pterodactyl\Models\ServiceOption;
use Illuminate\Database\Migrations\Migration;
namespace Pterodactyl\Models;
use Illuminate\Database\Eloquent\Model;
class ServicePack extends Model
class MigrateToNewServiceSystem extends Migration
{
/**
* The table associated with the model.
* Run the migrations.
*
* @var string
* @return void
*/
protected $table = 'service_packs';
/**
* Fields that are not mass assignable.
*
* @var array
*/
protected $guarded = ['id', 'created_at', 'updated_at'];
/**
* Cast values to correct type.
*
* @var array
*/
protected $casts = [
'option' => 'integer',
'build_memory' => 'integer',
'build_swap' => 'integer',
'build_cpu' => 'integer',
'build_io' => 'integer',
'selectable' => 'boolean',
'visible' => 'boolean',
];
/**
* Gets option associated with a service pack.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function option()
public function up()
{
return $this->belongsTo(ServiceOption::class);
DB::transaction(function () {
$service = Service::where('author', config('pterodactyl.service.core'))->where('folder', 'srcds')->first();
if (! $service) {
return;
}
$options = ServiceOption::where('service_id', $service->id)->get();
$options->each(function ($item) use ($options) {
if ($item->tag === 'srcds' && $item->name === 'Insurgency') {
$item->tag = 'insurgency';
} elseif ($item->tag === 'srcds' && $item->name === 'Team Fortress 2') {
$item->tag = 'tf2';
} elseif ($item->tag === 'srcds' && $item->name === 'Custom Source Engine Game') {
$item->tag = 'source';
}
});
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
// Not doing reversals right now...
}
}

View File

@ -0,0 +1,52 @@
<?php
use Illuminate\Support\Facades\Schema;
use Pterodactyl\Models\ServiceVariable;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class ChangeServiceVariablesValidationRules extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('service_variables', function (Blueprint $table) {
$table->renameColumn('regex', 'rules');
});
DB::transaction(function () {
foreach (ServiceVariable::all() as $variable) {
$variable->rules = ($variable->required) ? 'required|regex:' . $variable->rules : 'regex:' . $variable->regex;
$variable->save();
}
});
Schema::table('service_variables', function (Blueprint $table) {
$table->dropColumn('required');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('service_variables', function (Blueprint $table) {
$table->renameColumn('rules', 'regex');
$table->boolean('required')->default(true)->before('regex');
});
DB::transaction(function () {
foreach (ServiceVariable::all() as $variable) {
$variable->regex = str_replace(['required|regex:', 'regex:'], '', $variable->regex);
$variable->save();
}
});
}
}

View File

@ -0,0 +1,119 @@
<?php
use Pterodactyl\Models\Service;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class MoveFunctionsFromFileToDatabase extends Migration
{
private $default = <<<'EOF'
'use strict';
/**
* Pterodactyl - Daemon
* 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.
*/
const rfr = require('rfr');
const _ = require('lodash');
const Core = rfr('src/services/index.js');
class Service extends Core {}
module.exports = Service;
EOF;
private $default_mc = <<<'EOF'
'use strict';
/**
* Pterodactyl - Daemon
* 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.
*/
const rfr = require('rfr');
const _ = require('lodash');
const Core = rfr('src/services/index.js');
class Service extends Core {
onConsole(data) {
// Hide the output spam from Bungeecord getting pinged.
if (_.endsWith(data, '<-> InitialHandler has connected')) return;
return super.onConsole(data);
}
}
module.exports = Service;
EOF;
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('services', function (Blueprint $table) {
$table->text('index_file')->after('startup');
});
DB::transaction(function () {
Service::where('author', 'ptrdctyl-v040-11e6-8b77-86f30ca893d3')->where('folder', '!=', 'minecraft')->update([
'index_file' => $this->default,
]);
Service::where('author', 'ptrdctyl-v040-11e6-8b77-86f30ca893d3')->where('folder', 'minecraft')->update([
'index_file' => $this->default_mc,
]);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('services', function (Blueprint $table) {
$table->dropColumn('index_file');
});
}
}

View File

@ -0,0 +1,44 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class RenameServicePacksToSingluarPacks extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('service_packs', function (Blueprint $table) {
$table->dropForeign(['option_id']);
});
Schema::rename('service_packs', 'packs');
Schema::table('packs', function (Blueprint $table) {
$table->foreign('option_id')->references('id')->on('service_options');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('packs', function (Blueprint $table) {
$table->dropForeign(['option_id']);
});
Schema::rename('packs', 'service_packs');
Schema::table('service_packs', function (Blueprint $table) {
$table->foreign('option_id')->references('id')->on('service_options');
});
}
}

View File

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

View File

@ -21,15 +21,17 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
use Pterodactyl\Models;
use Illuminate\Database\Seeder;
use Pterodactyl\Models\Service;
use Pterodactyl\Models\ServiceOption;
use Pterodactyl\Models\ServiceVariable;
class MinecraftServiceTableSeeder extends Seeder
{
/**
* The core service ID.
*
* @var Models\Service
* @var \Pterodactyl\Models\Service
*/
protected $service;
@ -40,6 +42,47 @@ class MinecraftServiceTableSeeder extends Seeder
*/
protected $option = [];
private $default_mc = <<<'EOF'
'use strict';
/**
* Pterodactyl - Daemon
* 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.
*/
const rfr = require('rfr');
const _ = require('lodash');
const Core = rfr('src/services/index.js');
class Service extends Core {
onConsole(data) {
// Hide the output spam from Bungeecord getting pinged.
if (_.endsWith(data, '<-> InitialHandler has connected')) return;
return super.onConsole(data);
}
}
module.exports = Service;
EOF;
/**
* Run the database seeds.
*
@ -54,55 +97,76 @@ class MinecraftServiceTableSeeder extends Seeder
private function addCoreService()
{
$this->service = Models\Service::create([
'author' => 'ptrdctyl-v040-11e6-8b77-86f30ca893d3',
$this->service = Service::updateOrCreate([
'author' => config('pterodactyl.service.core'),
'folder' => 'minecraft',
], [
'name' => 'Minecraft',
'description' => 'Minecraft - the classic game from Mojang. With support for Vanilla MC, Spigot, and many others!',
'file' => 'minecraft',
'executable' => 'java',
'startup' => '-Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}',
'startup' => 'java -Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}',
'index_file' => $this->default_mc,
]);
}
private function addCoreOptions()
{
$this->option['vanilla'] = Models\ServiceOption::create([
$this->option['vanilla'] = ServiceOption::updateOrCreate([
'service_id' => $this->service->id,
'tag' => 'vanilla',
], [
'name' => 'Vanilla Minecraft',
'description' => 'Minecraft is a game about placing blocks and going on adventures. Explore randomly generated worlds and build amazing things from the simplest of homes to the grandest of castles. Play in Creative Mode with unlimited resources or mine deep in Survival Mode, crafting weapons and armor to fend off dangerous mobs. Do all this alone or with friends.',
'tag' => 'vanilla',
'docker_image' => 'quay.io/pterodactyl/minecraft',
'executable' => null,
'config_startup' => '{"done": ")! For help, type ", "userInteraction": [ "Go to eula.txt for more info."]}',
'config_logs' => '{"custom": false, "location": "logs/latest.log"}',
'config_files' => '{"server.properties":{"parser": "properties", "find":{"server-ip": "0.0.0.0", "enable-query": "true", "server-port": "{{server.build.default.port}}", "query.port": "{{server.build.default.port}}"}}}',
'config_stop' => 'stop',
'config_from' => null,
'startup' => null,
]);
$this->option['spigot'] = Models\ServiceOption::create([
$this->option['spigot'] = ServiceOption::updateOrCreate([
'service_id' => $this->service->id,
'tag' => 'spigot',
], [
'name' => 'Spigot',
'description' => 'Spigot is the most widely-used modded Minecraft server software in the world. It powers many of the top Minecraft server networks around to ensure they can cope with their huge player base and ensure the satisfaction of their players. Spigot works by reducing and eliminating many causes of lag, as well as adding in handy features and settings that help make your job of server administration easier.',
'tag' => 'spigot',
'docker_image' => 'quay.io/pterodactyl/minecraft:spigot',
'executable' => null,
'startup' => '-Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}',
]);
$this->option['sponge'] = Models\ServiceOption::create([
'service_id' => $this->service->id,
'name' => 'Sponge (SpongeVanilla)',
'description' => 'SpongeVanilla is the SpongeAPI implementation for Vanilla Minecraft.',
'tag' => 'sponge',
'docker_image' => 'quay.io/pterodactyl/minecraft:sponge',
'executable' => null,
'config_startup' => null,
'config_files' => '{"spigot.yml":{"parser": "yaml", "find":{"settings.restart-on-crash": "false"}}}',
'config_logs' => null,
'config_stop' => null,
'config_from' => $this->option['vanilla']->id,
'startup' => null,
]);
$this->option['bungeecord'] = Models\ServiceOption::create([
$this->option['sponge'] = ServiceOption::updateOrCreate([
'service_id' => $this->service->id,
'tag' => 'sponge',
], [
'name' => 'Sponge (SpongeVanilla)',
'description' => 'SpongeVanilla is the SpongeAPI implementation for Vanilla Minecraft.',
'docker_image' => 'quay.io/pterodactyl/minecraft:sponge',
'config_startup' => '{"userInteraction": [ "You need to agree to the EULA"]}',
'config_files' => null,
'config_logs' => null,
'config_stop' => null,
'config_from' => $this->option['vanilla']->id,
'startup' => null,
]);
$this->option['bungeecord'] = ServiceOption::updateOrCreate([
'service_id' => $this->service->id,
'tag' => 'bungeecord',
], [
'name' => 'Bungeecord',
'description' => 'For a long time, Minecraft server owners have had a dream that encompasses a free, easy, and reliable way to connect multiple Minecraft servers together. BungeeCord is the answer to said dream. Whether you are a small server wishing to string multiple game-modes together, or the owner of the ShotBow Network, BungeeCord is the ideal solution for you. With the help of BungeeCord, you will be able to unlock your community\'s full potential.',
'tag' => 'bungeecord',
'docker_image' => 'quay.io/pterodactyl/minecraft:bungeecord',
'executable' => null,
'config_startup' => '{"done": "Listening on ", "userInteraction": [ "Listening on /0.0.0.0:25577"]}',
'config_files' => '{"config.yml":{"parser": "yaml", "find":{"listeners[0].query_enabled": true, "listeners[0].query_port": "{{server.build.default.port}}", "listeners[0].host": "0.0.0.0:{{server.build.default.port}}", "servers.*.address":{"127.0.0.1": "{{config.docker.interface}}", "localhost": "{{config.docker.interface}}"}}}}',
'config_logs' => '{"custom": false, "location": "proxy.log.0"}',
'config_stop' => 'end',
'config_from' => null,
'startup' => null,
]);
}
@ -117,121 +181,121 @@ class MinecraftServiceTableSeeder extends Seeder
private function addVanillaVariables()
{
Models\ServiceVariable::create([
ServiceVariable::updateOrCreate([
'option_id' => $this->option['vanilla']->id,
'env_variable' => 'SERVER_JARFILE',
], [
'name' => 'Server Jar File',
'description' => 'The name of the server jarfile to run the server with.',
'env_variable' => 'SERVER_JARFILE',
'default_value' => 'server.jar',
'user_viewable' => 1,
'user_editable' => 1,
'required' => 1,
'regex' => '/^([\w\d._-]+)(\.jar)$/',
'rules' => 'required|regex:/^([\w\d._-]+)(\.jar)$/',
]);
Models\ServiceVariable::create([
ServiceVariable::updateOrCreate([
'option_id' => $this->option['vanilla']->id,
'env_variable' => 'VANILLA_VERSION',
], [
'name' => 'Server Version',
'description' => 'The version of Minecraft Vanilla to install. Use "latest" to install the latest version.',
'env_variable' => 'VANILLA_VERSION',
'default_value' => 'latest',
'user_viewable' => 1,
'user_editable' => 1,
'required' => 1,
'regex' => '/^(latest|[a-zA-Z0-9_\.-]{3,7})$/',
'rules' => 'required|string|between:3,7',
]);
}
private function addSpigotVariables()
{
Models\ServiceVariable::create([
ServiceVariable::updateOrCreate([
'option_id' => $this->option['spigot']->id,
'env_variable' => 'SERVER_JARFILE',
], [
'name' => 'Server Jar File',
'description' => 'The name of the server jarfile to run the server with.',
'env_variable' => 'SERVER_JARFILE',
'default_value' => 'server.jar',
'user_viewable' => 1,
'user_editable' => 1,
'required' => 1,
'regex' => '/^([\w\d._-]+)(\.jar)$/',
'rules' => 'required|regex:/^([\w\d._-]+)(\.jar)$/',
]);
Models\ServiceVariable::create([
ServiceVariable::updateOrCreate([
'option_id' => $this->option['spigot']->id,
'env_variable' => 'DL_VERSION',
], [
'name' => 'Spigot Version',
'description' => 'The version of Spigot to download (using the --rev tag). Use "latest" for latest.',
'env_variable' => 'DL_VERSION',
'default_value' => 'latest',
'user_viewable' => 1,
'user_editable' => 1,
'required' => 1,
'regex' => '/^(latest|[a-zA-Z0-9_\.-]{3,7})$/',
'rules' => 'required|string|between:3,7',
]);
Models\ServiceVariable::create([
ServiceVariable::updateOrCreate([
'option_id' => $this->option['spigot']->id,
'env_variable' => 'DL_PATH',
], [
'name' => 'Download Path',
'description' => 'A URL to use to download Spigot rather than building it on the server. This is not user viewable. Use <code>{{DL_VERSION}}</code> in the URL to automatically insert the assigned version into the URL. If you do not enter a URL Spigot will build directly in the container (this will fail on low memory containers).',
'env_variable' => 'DL_PATH',
'default_value' => '',
'user_viewable' => 0,
'user_editable' => 0,
'required' => 0,
'regex' => '/^(.*)$/',
'rules' => 'string',
]);
}
private function addSpongeVariables()
{
Models\ServiceVariable::create([
ServiceVariable::updateOrCreate([
'option_id' => $this->option['sponge']->id,
'env_variable' => 'SPONGE_VERSION',
], [
'name' => 'Sponge Version',
'description' => 'The version of SpongeVanilla to download and use.',
'env_variable' => 'SPONGE_VERSION',
'default_value' => '1.10.2-5.1.0-BETA-359',
'default_value' => '1.10.2-5.2.0-BETA-381',
'user_viewable' => 1,
'user_editable' => 0,
'required' => 1,
'regex' => '/^([a-zA-Z0-9.\-_]+)$/',
'rules' => 'required|regex:/^([a-zA-Z0-9.\-_]+)$/',
]);
Models\ServiceVariable::create([
ServiceVariable::updateOrCreate([
'option_id' => $this->option['sponge']->id,
'env_variable' => 'SERVER_JARFILE',
], [
'name' => 'Server Jar File',
'description' => 'The name of the Jarfile to use when running SpongeVanilla.',
'env_variable' => 'SERVER_JARFILE',
'default_value' => 'server.jar',
'user_viewable' => 1,
'user_editable' => 1,
'required' => 1,
'regex' => '/^([\w\d._-]+)(\.jar)$/',
'rules' => 'required|regex:/^([\w\d._-]+)(\.jar)$/',
]);
}
private function addBungeecordVariables()
{
Models\ServiceVariable::create([
ServiceVariable::updateOrCreate([
'option_id' => $this->option['bungeecord']->id,
'env_variable' => 'BUNGEE_VERSION',
], [
'name' => 'Bungeecord Version',
'description' => 'The version of Bungeecord to download and use.',
'env_variable' => 'BUNGEE_VERSION',
'default_value' => 'latest',
'user_viewable' => 1,
'user_editable' => 1,
'required' => 1,
'regex' => '/^(latest|[\d]{1,6})$/',
'rules' => 'required|alpha_num|between:1,6',
]);
Models\ServiceVariable::create([
ServiceVariable::updateOrCreate([
'option_id' => $this->option['bungeecord']->id,
'env_variable' => 'SERVER_JARFILE',
], [
'name' => 'Bungeecord Jar File',
'description' => 'The name of the Jarfile to use when running Bungeecord.',
'env_variable' => 'SERVER_JARFILE',
'default_value' => 'bungeecord.jar',
'user_viewable' => 1,
'user_editable' => 1,
'required' => 1,
'regex' => '/^([\w\d._-]+)(\.jar)$/',
'rules' => 'required|regex:/^([\w\d._-]+)(\.jar)$/',
]);
}
}

View File

@ -21,8 +21,10 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
use Pterodactyl\Models;
use Illuminate\Database\Seeder;
use Pterodactyl\Models\Service;
use Pterodactyl\Models\ServiceOption;
use Pterodactyl\Models\ServiceVariable;
class SourceServiceTableSeeder extends Seeder
{
@ -54,57 +56,78 @@ class SourceServiceTableSeeder extends Seeder
private function addCoreService()
{
$this->service = Models\Service::create([
'author' => 'ptrdctyl-v040-11e6-8b77-86f30ca893d3',
$this->service = Service::updateOrCreate([
'author' => config('pterodactyl.service.core'),
'folder' => 'srcds',
], [
'name' => 'Source Engine',
'description' => 'Includes support for most Source Dedicated Server games.',
'file' => 'srcds',
'executable' => './srcds_run',
'startup' => '-game {{SRCDS_GAME}} -console -port {{SERVER_PORT}} -strictportbind -norestart',
'startup' => './srcds_run -game {{SRCDS_GAME}} -console -port {{SERVER_PORT}} +ip 0.0.0.0 -strictportbind -norestart',
'index_file' => Service::defaultIndexFile(),
]);
}
private function addCoreOptions()
{
$this->option['insurgency'] = Models\ServiceOption::create([
'service_id' => $this->service->id,
'name' => 'Insurgency',
'description' => 'Take to the streets for intense close quarters combat, where a team\'s survival depends upon securing crucial strongholds and destroying enemy supply in this multiplayer and cooperative Source Engine based experience.',
'tag' => 'srcds',
'docker_image' => 'quay.io/pterodactyl/srcds',
'executable' => null,
'startup' => '-game {{SRCDS_GAME}} -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} -strictportbind -norestart',
]);
$this->option['tf2'] = Models\ServiceOption::create([
'service_id' => $this->service->id,
'name' => 'Team Fortress 2',
'description' => 'Team Fortress 2 is a team-based first-person shooter multiplayer video game developed and published by Valve Corporation. It is the sequel to the 1996 mod Team Fortress for Quake and its 1999 remake.',
'tag' => 'srcds',
'docker_image' => 'quay.io/pterodactyl/srcds',
'executable' => null,
'startup' => '-game {{SRCDS_GAME}} -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} -strictportbind -norestart',
]);
$this->option['ark'] = Models\ServiceOption::create([
'service_id' => $this->service->id,
'name' => 'Ark: Survival Evolved',
'description' => 'As a man or woman stranded, naked, freezing, and starving on the unforgiving shores of a mysterious island called ARK, use your skill and cunning to kill or tame and ride the plethora of leviathan dinosaurs and other primeval creatures roaming the land. Hunt, harvest resources, craft items, grow crops, research technologies, and build shelters to withstand the elements and store valuables, all while teaming up with (or preying upon) hundreds of other players to survive, dominate... and escape! — Gamepedia: ARK',
'tag' => 'ark',
'docker_image' => 'quay.io/pterodactyl/srcds:ark',
'executable' => './ShooterGameServer',
'startup' => 'TheIsland?listen?ServerPassword={{ARK_PASSWORD}}?ServerAdminPassword={{ARK_ADMIN_PASSWORD}}?Port={{SERVER_PORT}}?MaxPlayers={{SERVER_MAX_PLAYERS}}',
]);
$this->option['custom'] = Models\ServiceOption::create([
$this->option['source'] = ServiceOption::updateOrCreate([
'service_id' => $this->service->id,
'tag' => 'source',
], [
'name' => 'Custom Source Engine Game',
'description' => 'This option allows modifying the startup arguments and other details to run a custo SRCDS based game on the panel.',
'tag' => 'srcds',
'docker_image' => 'quay.io/pterodactyl/srcds',
'executable' => null,
'config_startup' => '{"done": "Assigned anonymous gameserver", "userInteraction": []}',
'config_files' => '{}',
'config_logs' => '{"custom": true, "location": "logs/latest.log"}',
'config_stop' => 'quit',
'config_from' => null,
'startup' => null,
]);
$this->option['insurgency'] = ServiceOption::updateOrCreate([
'service_id' => $this->service->id,
'tag' => 'insurgency',
], [
'name' => 'Insurgency',
'description' => 'Take to the streets for intense close quarters combat, where a team\'s survival depends upon securing crucial strongholds and destroying enemy supply in this multiplayer and cooperative Source Engine based experience.',
'docker_image' => 'quay.io/pterodactyl/srcds',
'config_startup' => null,
'config_files' => null,
'config_logs' => null,
'config_stop' => null,
'config_from' => $this->option['source']->id,
'startup' => './srcds_run -game {{SRCDS_GAME}} -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} +ip 0.0.0.0 -strictportbind -norestart',
]);
$this->option['tf2'] = ServiceOption::updateOrCreate([
'service_id' => $this->service->id,
'tag' => 'tf2',
], [
'name' => 'Team Fortress 2',
'description' => 'Team Fortress 2 is a team-based first-person shooter multiplayer video game developed and published by Valve Corporation. It is the sequel to the 1996 mod Team Fortress for Quake and its 1999 remake.',
'docker_image' => 'quay.io/pterodactyl/srcds',
'config_startup' => null,
'config_files' => null,
'config_logs' => null,
'config_stop' => null,
'config_from' => $this->option['source']->id,
'startup' => './srcds_run -game {{SRCDS_GAME}} -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} +ip 0.0.0.0 -strictportbind -norestart',
]);
$this->option['ark'] = ServiceOption::updateOrCreate([
'service_id' => $this->service->id,
'tag' => 'ark',
], [
'name' => 'Ark: Survival Evolved',
'description' => 'As a man or woman stranded, naked, freezing, and starving on the unforgiving shores of a mysterious island called ARK, use your skill and cunning to kill or tame and ride the plethora of leviathan dinosaurs and other primeval creatures roaming the land. Hunt, harvest resources, craft items, grow crops, research technologies, and build shelters to withstand the elements and store valuables, all while teaming up with (or preying upon) hundreds of other players to survive, dominate... and escape! — Gamepedia: ARK',
'docker_image' => 'quay.io/pterodactyl/srcds:ark',
'config_startup' => '{"done": "Setting breakpad minidump AppID"}',
'config_files' => null,
'config_logs' => null,
'config_stop' => '^C',
'config_from' => $this->option['source']->id,
'startup' => './ShooterGame/Binaries/Linux/ShooterGameServer TheIsland?listen?ServerPassword={{ARK_PASSWORD}}?ServerAdminPassword={{ARK_ADMIN_PASSWORD}}?Port={{SERVER_PORT}}?MaxPlayers={{SERVER_MAX_PLAYERS}}',
]);
}
private function addVariables()
@ -117,145 +140,145 @@ class SourceServiceTableSeeder extends Seeder
private function addInsurgencyVariables()
{
Models\ServiceVariable::create([
ServiceVariable::updateOrCreate([
'option_id' => $this->option['insurgency']->id,
'env_variable' => 'SRCDS_APPID',
], [
'name' => 'Game ID',
'description' => 'The ID corresponding to the game to download and run using SRCDS.',
'env_variable' => 'SRCDS_APPID',
'default_value' => '17705',
'user_viewable' => 1,
'user_editable' => 0,
'required' => 1,
'regex' => '/^(17705)$/',
'rules' => 'required|regex:/^(17705)$/',
]);
Models\ServiceVariable::create([
ServiceVariable::updateOrCreate([
'option_id' => $this->option['insurgency']->id,
'env_variable' => 'SRCDS_GAME',
], [
'name' => 'Game Name',
'description' => 'The name corresponding to the game to download and run using SRCDS.',
'env_variable' => 'SRCDS_GAME',
'default_value' => 'insurgency',
'user_viewable' => 1,
'user_editable' => 0,
'required' => 1,
'regex' => '/^(insurgency)$/',
'rules' => 'required|regex:/^(insurgency)$/',
]);
Models\ServiceVariable::create([
ServiceVariable::updateOrCreate([
'option_id' => $this->option['insurgency']->id,
'env_variable' => 'SRCDS_MAP',
], [
'name' => 'Default Map',
'description' => 'The default map to use when starting the server.',
'env_variable' => 'SRCDS_MAP',
'default_value' => 'sinjar',
'user_viewable' => 1,
'user_editable' => 1,
'required' => 1,
'regex' => '/^(\w{1,20})$/',
'rules' => 'required|regex:/^(\w{1,20})$/',
]);
}
private function addTF2Variables()
{
Models\ServiceVariable::create([
ServiceVariable::updateOrCreate([
'option_id' => $this->option['tf2']->id,
'env_variable' => 'SRCDS_APPID',
], [
'name' => 'Game ID',
'description' => 'The ID corresponding to the game to download and run using SRCDS.',
'env_variable' => 'SRCDS_APPID',
'default_value' => '232250',
'user_viewable' => 1,
'user_editable' => 0,
'required' => 1,
'regex' => '/^(232250)$/',
'rules' => 'required|regex:/^(232250)$/',
]);
Models\ServiceVariable::create([
ServiceVariable::updateOrCreate([
'option_id' => $this->option['tf2']->id,
'env_variable' => 'SRCDS_GAME',
], [
'name' => 'Game Name',
'description' => 'The name corresponding to the game to download and run using SRCDS.',
'env_variable' => 'SRCDS_GAME',
'default_value' => 'tf',
'user_viewable' => 1,
'user_editable' => 0,
'required' => 1,
'regex' => '/^(tf)$/',
'rules' => 'required|regex:/^(tf)$/',
]);
Models\ServiceVariable::create([
ServiceVariable::updateOrCreate([
'option_id' => $this->option['tf2']->id,
'env_variable' => 'SRCDS_MAP',
], [
'name' => 'Default Map',
'description' => 'The default map to use when starting the server.',
'env_variable' => 'SRCDS_MAP',
'default_value' => 'cp_dustbowl',
'user_viewable' => 1,
'user_editable' => 1,
'required' => 1,
'regex' => '/^(\w{1,20})$/',
'rules' => 'required|regex:/^(\w{1,20})$/',
]);
}
private function addArkVariables()
{
DB::table('service_variables')->insert([
ServiceVariable::updateOrCreate([
'option_id' => $this->option['ark']->id,
'env_variable' => 'ARK_PASSWORD',
], [
'name' => 'Server Password',
'description' => 'If specified, players must provide this password to join the server.',
'env_variable' => 'ARK_PASSWORD',
'default_value' => '',
'user_viewable' => 1,
'user_editable' => 1,
'required' => 0,
'regex' => '/^(\w\.*)$/',
'rules' => 'alpha_dash|between:1,100',
]);
DB::table('service_variables')->insert([
ServiceVariable::updateOrCreate([
'option_id' => $this->option['ark']->id,
'env_variable' => 'ARK_ADMIN_PASSWORD',
], [
'name' => 'Admin Password',
'description' => 'If specified, players must provide this password (via the in-game console) to gain access to administrator commands on the server.',
'env_variable' => 'ARK_ADMIN_PASSWORD',
'default_value' => '',
'user_viewable' => 1,
'user_editable' => 1,
'required' => 0,
'regex' => '/^(\w\.*)$/',
'rules' => 'alpha_dash|between:1,100',
]);
DB::table('service_variables')->insert([
ServiceVariable::updateOrCreate([
'option_id' => $this->option['ark']->id,
'env_variable' => 'SERVER_MAX_PLAYERS',
], [
'name' => 'Maximum Players',
'description' => 'Specifies the maximum number of players that can play on the server simultaneously.',
'env_variable' => 'SERVER_MAX_PLAYERS',
'default_value' => 20,
'user_viewable' => 1,
'user_editable' => 1,
'required' => 1,
'regex' => '/^(\d{1,4})$/',
'rules' => 'required|numeric|digits_between:1,4',
]);
}
private function addCustomVariables()
{
Models\ServiceVariable::create([
'option_id' => $this->option['custom']->id,
ServiceVariable::updateOrCreate([
'option_id' => $this->option['source']->id,
'env_variable' => 'SRCDS_APPID',
], [
'name' => 'Game ID',
'description' => 'The ID corresponding to the game to download and run using SRCDS.',
'env_variable' => 'SRCDS_APPID',
'default_value' => '',
'user_viewable' => 1,
'user_editable' => 0,
'required' => 1,
'regex' => '/^(\d){1,6}$/',
'rules' => 'required|numeric|digits_between:1,6',
]);
Models\ServiceVariable::create([
'option_id' => $this->option['custom']->id,
ServiceVariable::updateOrCreate([
'option_id' => $this->option['source']->id,
'env_variable' => 'SRCDS_GAME',
], [
'name' => 'Game Name',
'description' => 'The name corresponding to the game to download and run using SRCDS.',
'env_variable' => 'SRCDS_GAME',
'default_value' => '',
'user_viewable' => 1,
'user_editable' => 0,
'required' => 1,
'regex' => '/^(.*)$/',
'rules' => 'required|alpha_dash|between:1,100',
]);
}
}

View File

@ -21,8 +21,10 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
use Pterodactyl\Models;
use Illuminate\Database\Seeder;
use Pterodactyl\Models\Service;
use Pterodactyl\Models\ServiceOption;
use Pterodactyl\Models\ServiceVariable;
class TerrariaServiceTableSeeder extends Seeder
{
@ -54,53 +56,58 @@ class TerrariaServiceTableSeeder extends Seeder
private function addCoreService()
{
$this->service = Models\Service::create([
'author' => 'ptrdctyl-v040-11e6-8b77-86f30ca893d3',
$this->service = Service::updateOrCreate([
'author' => config('pterodactyl.service.core'),
'folder' => 'terraria',
], [
'name' => 'Terraria',
'description' => 'Terraria is a land of adventure! A land of mystery! A land that\'s yours to shape, defend, and enjoy. Your options in Terraria are limitless. Are you an action gamer with an itchy trigger finger? A master builder? A collector? An explorer? There\'s something for everyone.',
'file' => 'terraria',
'executable' => 'TerrariaServer.exe',
'startup' => '-port {{SERVER_PORT}} -autocreate 2 -worldname World',
'startup' => 'mono TerrariaServer.exe -port {{SERVER_PORT}} -autocreate 2 -worldname World',
]);
}
private function addCoreOptions()
{
$this->option['tshock'] = Models\ServiceOption::create([
$this->option['tshock'] = ServiceOption::updateOrCreate([
'service_id' => $this->service->id,
'tag' => 'tshock',
], [
'name' => 'Terraria Server (TShock)',
'description' => 'TShock is a server modification for Terraria, written in C#, and based upon the Terraria Server API. It uses JSON for configuration management, and offers several features not present in the Terraria Server normally.',
'tag' => 'tshock',
'docker_image' => 'quay.io/pterodactyl/terraria:tshock',
'executable' => '',
'startup' => '',
'config_startup' => '{"userInteraction": [ "You need to agree to the EULA"]}',
'config_startup' => '{"done": "Type \'help\' for a list of commands", "userInteraction": []}',
'config_files' => '{"tshock/config.json":{"parser": "json", "find":{"ServerPort": "{{server.build.default.port}}", "MaxSlots": "{{server.build.env.MAX_SLOTS}}"}}}',
'config_logs' => '{"custom": false, "location": "ServerLog.txt"}',
'config_stop' => 'exit',
'startup' => null,
]);
}
private function addVariables()
{
Models\ServiceVariable::create([
ServiceVariable::updateOrCreate([
'option_id' => $this->option['tshock']->id,
'env_variable' => 'T_VERSION',
], [
'name' => 'TShock Version',
'description' => 'Which version of TShock to install and use.',
'env_variable' => 'T_VERSION',
'default_value' => '4.3.17',
'default_value' => '4.3.22',
'user_viewable' => 1,
'user_editable' => 1,
'required' => 1,
'regex' => '/^([0-9_\.-]{5,10})$/',
'rules' => 'required|regex:/^([0-9_\.-]{5,10})$/',
]);
Models\ServiceVariable::create([
ServiceVariable::updateOrCreate([
'option_id' => $this->option['tshock']->id,
'env_variable' => 'MAX_SLOTS',
], [
'name' => 'Maximum Slots',
'description' => 'Total number of slots to allow on the server.',
'env_variable' => 'MAX_SLOTS',
'default_value' => '20',
'default_value' => 20,
'user_viewable' => 1,
'user_editable' => 0,
'required' => 1,
'regex' => '/^(\d){1,3}$/',
'rules' => 'required|numeric|digits_between:1,3',
]);
}
}

View File

@ -21,15 +21,17 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
use Pterodactyl\Models;
use Illuminate\Database\Seeder;
use Pterodactyl\Models\Service;
use Pterodactyl\Models\ServiceOption;
use Pterodactyl\Models\ServiceVariable;
class VoiceServiceTableSeeder extends Seeder
{
/**
* The core service ID.
*
* @var Models\Service
* @var Service
*/
protected $service;
@ -54,75 +56,85 @@ class VoiceServiceTableSeeder extends Seeder
private function addCoreService()
{
$this->service = Models\Service::create([
'author' => 'ptrdctyl-v040-11e6-8b77-86f30ca893d3',
$this->service = Service::updateOrCreate([
'author' => config('pterodactyl.service.core'),
'folder' => 'voice',
], [
'name' => 'Voice Servers',
'description' => 'Voice servers such as Mumble and Teamspeak 3.',
'file' => 'voice',
'executable' => '',
'startup' => '',
]);
}
private function addCoreOptions()
{
$this->option['mumble'] = Models\ServiceOption::create([
$this->option['mumble'] = ServiceOption::updateOrCreate([
'service_id' => $this->service->id,
'tag' => 'mumble',
], [
'name' => 'Mumble Server',
'description' => 'Mumble is an open source, low-latency, high quality voice chat software primarily intended for use while gaming.',
'tag' => 'mumble',
'docker_image' => 'quay.io/pterodactyl/voice:mumble',
'executable' => './murmur.x86',
'startup' => '-fg',
'config_startup' => '{"done": "Server listening on", "userInteraction": [ "Generating new server certificate"]}',
'config_files' => '{"murmur.ini":{"parser": "ini", "find":{"logfile": "murmur.log", "port": "{{server.build.default.port}}", "host": "0.0.0.0", "users": "{{server.build.env.MAX_USERS}}"}}}',
'config_logs' => '{"custom": true, "location": "logs/murmur.log"}',
'config_stop' => '^C',
'config_from' => null,
'startup' => './murmur.x86 -fg',
]);
$this->option['ts3'] = Models\ServiceOption::create([
$this->option['ts3'] = ServiceOption::updateOrCreate([
'service_id' => $this->service->id,
'tag' => 'ts3',
], [
'name' => 'Teamspeak3 Server',
'description' => 'VoIP software designed with security in mind, featuring crystal clear voice quality, endless customization options, and scalabilty up to thousands of simultaneous users.',
'tag' => 'ts3',
'docker_image' => 'quay.io/pterodactyl/voice:ts3',
'executable' => './ts3server_minimal_runscript.sh',
'startup' => 'default_voice_port={{SERVER_PORT}} query_port={{SERVER_PORT}}',
'config_startup' => '{"done": "listening on 0.0.0.0:", "userInteraction": []}',
'config_files' => '{"ts3server.ini":{"parser": "ini", "find":{"default_voice_port": "{{server.build.default.port}}", "voice_ip": "0.0.0.0", "query_port": "{{server.build.default.port}}", "query_ip": "0.0.0.0"}}}',
'config_logs' => '{"custom": true, "location": "logs/ts3.log"}',
'config_stop' => '^C',
'config_from' => null,
'startup' => './ts3server_minimal_runscript.sh default_voice_port={{SERVER_PORT}} query_port={{SERVER_PORT}}',
]);
}
private function addVariables()
{
Models\ServiceVariable::create([
ServiceVariable::updateOrCreate([
'option_id' => $this->option['mumble']->id,
'env_variable' => 'MAX_USERS',
], [
'name' => 'Maximum Users',
'description' => 'Maximum concurrent users on the mumble server.',
'env_variable' => 'MAX_USERS',
'default_value' => '100',
'default_value' => 100,
'user_viewable' => 1,
'user_editable' => 0,
'required' => 1,
'regex' => '/^(\d){1,6}$/',
'rules' => 'required|numeric|digits_between:1,5',
]);
Models\ServiceVariable::create([
ServiceVariable::updateOrCreate([
'option_id' => $this->option['mumble']->id,
'env_variable' => 'MUMBLE_VERSION',
], [
'name' => 'Server Version',
'description' => 'Version of Mumble Server to download and use.',
'env_variable' => 'MUMBLE_VERSION',
'default_value' => '1.2.16',
'default_value' => '1.2.19',
'user_viewable' => 1,
'user_editable' => 1,
'required' => 1,
'regex' => '/^([0-9_\.-]{5,8})$/',
'rules' => 'required|regex:/^([0-9_\.-]{5,8})$/',
]);
Models\ServiceVariable::create([
ServiceVariable::updateOrCreate([
'option_id' => $this->option['ts3']->id,
'env_variable' => 'T_VERSION',
], [
'name' => 'Server Version',
'description' => 'The version of Teamspeak 3 to use when running the server.',
'env_variable' => 'T_VERSION',
'default_value' => '3.0.13.4',
'default_value' => '3.1.1.1',
'user_viewable' => 1,
'user_editable' => 1,
'required' => 1,
'regex' => '/^([0-9_\.-]{5,10})$/',
'rules' => 'required|regex:/^([0-9_\.-]{5,10})$/',
]);
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,231 @@
/**
* Bootsnip - "Bootstrap Checkboxes/Radios"
* Bootstrap 3.2.0 Snippet by i-heart-php <http://bootsnipp.com/i-heart-php>
*
* Copyright (c) 2013 Bootsnipp.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.
*/
.checkbox {
padding-left: 20px;
}
.checkbox label {
display: inline-block;
position: relative;
padding-left: 5px;
}
.checkbox label::before {
content: "";
display: inline-block;
position: absolute;
width: 17px;
height: 17px;
left: 0;
margin-left: -20px;
border: 1px solid #cccccc;
border-radius: 3px;
background-color: #fff;
-webkit-transition: border 0.15s ease-in-out, color 0.15s ease-in-out;
-o-transition: border 0.15s ease-in-out, color 0.15s ease-in-out;
transition: border 0.15s ease-in-out, color 0.15s ease-in-out;
}
.checkbox label::after {
display: inline-block;
position: absolute;
width: 16px;
height: 16px;
left: 0;
top: 0;
margin-left: -20px;
padding-left: 3px;
padding-top: 1px;
font-size: 11px;
color: #555555;
}
.checkbox input[type="checkbox"] {
opacity: 0;
}
.checkbox input[type="checkbox"]:focus + label::before {
outline: thin dotted;
outline: 5px auto -webkit-focus-ring-color;
outline-offset: -2px;
}
.checkbox input[type="checkbox"]:checked + label::after {
font-family: 'FontAwesome';
content: "\f00c";
}
.checkbox input[type="checkbox"]:disabled + label {
opacity: 0.65;
}
.checkbox input[type="checkbox"]:disabled + label::before {
background-color: #eeeeee;
cursor: not-allowed;
}
.checkbox.checkbox-circle label::before {
border-radius: 50%;
}
.checkbox.checkbox-inline {
margin-top: 0;
}
.checkbox-primary input[type="checkbox"]:checked + label::before {
background-color: #428bca;
border-color: #428bca;
}
.checkbox-primary input[type="checkbox"]:checked + label::after {
color: #fff;
}
.checkbox-danger input[type="checkbox"]:checked + label::before {
background-color: #d9534f;
border-color: #d9534f;
}
.checkbox-danger input[type="checkbox"]:checked + label::after {
color: #fff;
}
.checkbox-info input[type="checkbox"]:checked + label::before {
background-color: #5bc0de;
border-color: #5bc0de;
}
.checkbox-info input[type="checkbox"]:checked + label::after {
color: #fff;
}
.checkbox-warning input[type="checkbox"]:checked + label::before {
background-color: #f0ad4e;
border-color: #f0ad4e;
}
.checkbox-warning input[type="checkbox"]:checked + label::after {
color: #fff;
}
.checkbox-success input[type="checkbox"]:checked + label::before {
background-color: #5cb85c;
border-color: #5cb85c;
}
.checkbox-success input[type="checkbox"]:checked + label::after {
color: #fff;
}
.radio {
padding-left: 20px;
}
.radio label {
display: inline-block;
position: relative;
padding-left: 5px;
}
.radio label::before {
content: "";
display: inline-block;
position: absolute;
width: 17px;
height: 17px;
left: 0;
margin-left: -20px;
border: 1px solid #cccccc;
border-radius: 50%;
background-color: #fff;
-webkit-transition: border 0.15s ease-in-out;
-o-transition: border 0.15s ease-in-out;
transition: border 0.15s ease-in-out;
}
.radio label::after {
display: inline-block;
position: absolute;
content: " ";
width: 11px;
height: 11px;
left: 3px;
top: 3px;
margin-left: -20px;
border-radius: 50%;
background-color: #555555;
-webkit-transform: scale(0, 0);
-ms-transform: scale(0, 0);
-o-transform: scale(0, 0);
transform: scale(0, 0);
-webkit-transition: -webkit-transform 0.1s cubic-bezier(0.8, -0.33, 0.2, 1.33);
-moz-transition: -moz-transform 0.1s cubic-bezier(0.8, -0.33, 0.2, 1.33);
-o-transition: -o-transform 0.1s cubic-bezier(0.8, -0.33, 0.2, 1.33);
transition: transform 0.1s cubic-bezier(0.8, -0.33, 0.2, 1.33);
}
.radio input[type="radio"] {
opacity: 0;
}
.radio input[type="radio"]:focus + label::before {
outline: thin dotted;
outline: 5px auto -webkit-focus-ring-color;
outline-offset: -2px;
}
.radio input[type="radio"]:checked + label::after {
-webkit-transform: scale(1, 1);
-ms-transform: scale(1, 1);
-o-transform: scale(1, 1);
transform: scale(1, 1);
}
.radio input[type="radio"]:disabled + label {
opacity: 0.65;
}
.radio input[type="radio"]:disabled + label::before {
cursor: not-allowed;
}
.radio.radio-inline {
margin-top: 0;
}
.radio-primary input[type="radio"] + label::after {
background-color: #428bca;
}
.radio-primary input[type="radio"]:checked + label::before {
border-color: #428bca;
}
.radio-primary input[type="radio"]:checked + label::after {
background-color: #428bca;
}
.radio-danger input[type="radio"] + label::after {
background-color: #d9534f;
}
.radio-danger input[type="radio"]:checked + label::before {
border-color: #d9534f;
}
.radio-danger input[type="radio"]:checked + label::after {
background-color: #d9534f;
}
.radio-info input[type="radio"] + label::after {
background-color: #5bc0de;
}
.radio-info input[type="radio"]:checked + label::before {
border-color: #5bc0de;
}
.radio-info input[type="radio"]:checked + label::after {
background-color: #5bc0de;
}
.radio-warning input[type="radio"] + label::after {
background-color: #f0ad4e;
}
.radio-warning input[type="radio"]:checked + label::before {
border-color: #f0ad4e;
}
.radio-warning input[type="radio"]:checked + label::after {
background-color: #f0ad4e;
}
.radio-success input[type="radio"] + label::after {
background-color: #5cb85c;
}
.radio-success input[type="radio"]:checked + label::before {
border-color: #5cb85c;
}
.radio-success input[type="radio"]:checked + label::after {
background-color: #5cb85c;
}

View File

@ -20,6 +20,8 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
@import 'checkbox.css';
.login-box, .register-box {
width: 40%;
margin: 7% auto
@ -266,3 +268,7 @@ span[aria-labelledby="select2-pUserId-container"] {
.terminal-notify:hover {
opacity: .9;
}
.no-margin-bottom {
margin-bottom: 0 !important;
}

View File

@ -234,7 +234,7 @@ return [
'edit_params' => 'Edit Parameters',
'update' => 'Update Startup Parameters',
'startup_var' => 'Startup Command Variable',
'startup_regex' => 'Verification Regex',
'startup_regex' => 'Input Rules',
],
'sftp' => [
'header' => 'SFTP Configuration',

View File

@ -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 Packs
@endsection
@section('content-header')
<h1>Packs<small>All service packs available on the system.</small></h1>
<ol class="breadcrumb">
<li><a href="{{ route('admin.index') }}">Admin</a></li>
<li class="active">Packs</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">Pack List</h3>
<div class="box-tools">
<form action="{{ route('admin.packs') }}" 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 Packs">
<div class="input-group-btn">
<button type="submit" class="btn btn-default"><i class="fa fa-search"></i></button>
<a href="{{ route('admin.packs.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>Pack Name</th>
<th>Version</th>
<th>Description</th>
<th>Option</td>
<th class="text-center">Servers</th>
</tr>
@foreach ($packs as $pack)
<tr>
<td class="middle" data-toggle="tooltip" data-placement="right" title="{{ $pack->uuid }}"><code>{{ $pack->id }}</code></td>
<td class="middle"><a href="{{ route('admin.packs.view', $pack->id) }}">{{ $pack->name }}</a></td>
<td class="middle"><code>{{ $pack->version }}</code></td>
<td class="col-md-6">{{ str_limit($pack->description, 150) }}</td>
<td class="middle"><a href="{{ route('admin.services.option.view', $pack->option->id) }}">{{ $pack->option->name }}</a></td>
<td class="middle text-center">{{ $pack->servers_count }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@if ($packs->hasPages())
<div class="box-footer with-border">
<div class="col-md-12 text-center">{!! $packs->render() !!}</div>
</div>
@endif
</div>
</div>
</div>
@endsection

View File

@ -1,7 +1,7 @@
<div class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<form action="{{ route('admin.services.packs.uploadForm') }}" method="POST" enctype="multipart/form-data">
<form action="{{ route('admin.packs.new') }}" method="POST" enctype="multipart/form-data">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">Install Pack from Template</h4>
@ -10,15 +10,17 @@
<div class="well" style="margin-bottom:0">
<div class="row">
<div class="col-md-12">
<label class="control-label">Associated Service Option:</label>
<select name="option" class="form-control">
<label for="pOptionIdModal" class="form-label">Associated Service Option:</label>
<select id="pOptionIdModal" name="option_id" class="form-control">
@foreach($services as $service)
<option disabled>{{ $service->name }}</option>
<optgroup label="{{ $service->name }}">
@foreach($service->options as $option)
<option value="{{ $option->id }}" @if((int) request()->option === $option->id)selected="selected"@endif>&nbsp;&nbsp; -- {{ $option->name }}</option>
<option value="{{ $option->id }}">{{ $option->name }}</option>
@endforeach
</optgroup>
@endforeach
</select>
<p class="text-muted small">The option that this pack is assocaited with. Only servers that are assigned this option will be able to access this pack.</p>
</div>
</div>
<div class="row" style="margin-top:15px;">
@ -27,7 +29,7 @@
<div class="form-group col-md-12">
<label class="control-label">Package Archive:</label>
<input name="file_upload" type="file" accept=".zip,.json, application/json, application/zip" />
<p class="text-muted"><small>This file should be either the <code>.json</code> template file, or a <code>.zip</code> pack archive containing <code>archive.(zip|tar.gz)</code> and <code>import.json</code> within.<br /><br />This server is currently configured with the following limits: <code>upload_max_filesize={{ ini_get('upload_max_filesize') }}</code> and <code>post_max_size={{ ini_get('post_max_size') }}</code>. If your file is larger than either of those values this request will fail.</small></p>
<p class="text-muted"><small>This file should be either the <code>.json</code> template file, or a <code>.zip</code> pack archive containing <code>archive.tar.gz</code> and <code>import.json</code> within.<br /><br />This server is currently configured with the following limits: <code>upload_max_filesize={{ ini_get('upload_max_filesize') }}</code> and <code>post_max_size={{ ini_get('post_max_size') }}</code>. If your file is larger than either of those values this request will fail.</small></p>
</div>
</div>
</div>
@ -36,8 +38,8 @@
</div>
<div class="modal-footer">
{!! csrf_field() !!}
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal">Cancel</button>
<input type="submit" class="btn btn-primary btn-sm" value="Install" />
<button type="button" class="btn btn-default btn-sm pull-left" data-dismiss="modal">Cancel</button>
<button type="submit" name="action" value="from_template" class="btn btn-primary btn-sm">Install</button>
</div>
</form>
</div>

View File

@ -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')
Packs &rarr; New
@endsection
@section('content-header')
<h1>New Pack<small>Create a new pack on the system.</small></h1>
<ol class="breadcrumb">
<li><a href="{{ route('admin.index') }}">Admin</a></li>
<li><a href="{{ route('admin.packs') }}">Packs</a></li>
<li class="active">New</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.packs.new') }}">Configure Manually</a></li>
<li><a href="#modal" id="toggleModal">Install From Template</a></li>
</ul>
</div>
</div>
</div>
<form action="{{ route('admin.packs.new') }}" method="POST" enctype="multipart/form-data">
<div class="row">
<div class="col-md-6">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">Pack Details</h3>
</div>
<div class="box-body">
<div class="form-group">
<label for="pName" class="form-label">Name</label>
<input name="name" type="text" id="pName" class="form-control" value="{{ old('name') }}" />
<p class="text-muted small">A short but descriptive name of what this pack is. For example, <code>Counter Strike: Source</code> if it is a Counter Strike package.</p>
</div>
<div class="form-group">
<label for="pDescription" class="form-label">Description</label>
<textarea name="description" id="pDescription" class="form-control" rows="8">{{ old('description') }}</textarea>
</div>
<div class="form-group">
<label for="pVersion" class="form-label">Version</label>
<input type="text" name="version" id="pVersion" class="form-control" value="{{ old('version') }}" />
<p class="text-muted small">The version of this package, or the version of the files contained within the package.</p>
</div>
<div class="form-group">
<label for="pOptionId" class="form-label">Associated Option</label>
<select id="pOptionId" name="option_id" class="form-control">
@foreach($services as $service)
<optgroup label="{{ $service->name }}">
@foreach($service->options as $option)
<option value="{{ $option->id }}">{{ $option->name }}</option>
@endforeach
</optgroup>
@endforeach
</select>
<p class="text-muted small">The option that this pack is assocaited with. Only servers that are assigned this option will be able to access this pack.</p>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">Pack Configuration</h3>
</div>
<div class="box-body">
<div class="form-group">
<div class="checkbox checkbox-primary no-margin-bottom">
<input id="pSelectable" name="selectable" type="checkbox" value="1" checked/>
<label for="pSelectable">
Selectable
</label>
</div>
<p class="text-muted small">Check this box if user should be able to select this pack to install on their servers.</p>
</div>
<div class="form-group">
<div class="checkbox checkbox-primary no-margin-bottom">
<input id="pVisible" name="visible" type="checkbox" value="1" checked/>
<label for="pVisible">
Visible
</label>
</div>
<p class="text-muted small">Check this box if this pack is visible in the dropdown menu. If this pack is assigned to a server it will be visible regardless of this setting.</p>
</div>
<div class="form-group">
<div class="checkbox checkbox-warning no-margin-bottom">
<input id="pLocked" name="locked" type="checkbox" value="1"/>
<label for="pLocked">
Locked
</label>
</div>
<p class="text-muted small">Check this box if servers assigned this pack should not be able to switch to a different pack.</p>
</div>
<hr />
<div class="form-group no-margin-bottom">
<label for="pFileUpload" class="form-label">Pack Archive</label>
<input type="file" accept=".tar.gz, application/gzip" name="file_upload" class="well well-sm" style="width:100%"/>
<p class="text-muted small">This package file must be a <code>.tar.gz</code> archive of pack files to be decompressed into the server folder.</p>
<p class="text-muted small">If your file is larger than <code>50MB</code> it is recommended to upload it using SFTP. Once you have added this pack to the system, a path will be provided where you should upload the file.</p>
<div class="callout callout-info callout-slim no-margin-bottom">
<p class="text-muted small"><strong>This server is currently configured with the following limits:</strong><br /><code>upload_max_filesize={{ ini_get('upload_max_filesize') }}</code><br /><code>post_max_size={{ ini_get('post_max_size') }}</code><br /><br />If your file is larger than either of those values this request will fail.</p>
</div>
</div>
</div>
<div class="box-footer with-border">
{!! csrf_field() !!}
<button class="btn btn-sm btn-success pull-right" type="submit">Create Pack</button>
</div>
</div>
</div>
</div>
</form>
@endsection
@section('footer-scripts')
@parent
<script>
$('#pOptionId').select2();
$('#toggleModal').on('click', function (event) {
event.preventDefault();
$.ajax({
method: 'GET',
url: Router.route('admin.packs.new.template'),
headers: { 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content') },
}).fail(function (jqXhr) {
console.error(jqXhr);
alert('There was an error trying to create the upload modal.');
}).success(function (data) {
$(data).modal();
$('#pOptionIdModal').select2();
});
});
</script>
@endsection

View File

@ -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')
Packs &rarr; View &rarr; {{ $pack->name }}
@endsection
@section('content-header')
<h1>{{ $pack->name }}<small>{{ str_limit($pack->description, 60) }}</small></h1>
<ol class="breadcrumb">
<li><a href="{{ route('admin.index') }}">Admin</a></li>
<li><a href="{{ route('admin.packs') }}">Packs</a></li>
<li class="active">{{ $pack->name }}</li>
</ol>
@endsection
@section('content')
<form action="{{ route('admin.packs.view', $pack->id) }}" method="POST">
<div class="row">
<div class="col-md-6">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">Pack Details</h3>
</div>
<div class="box-body">
<div class="form-group">
<label for="pName" class="form-label">Name</label>
<input name="name" type="text" id="pName" class="form-control" value="{{ $pack->name }}" />
<p class="text-muted small">A short but descriptive name of what this pack is. For example, <code>Counter Strike: Source</code> if it is a Counter Strike package.</p>
</div>
<div class="form-group">
<label for="pDescription" class="form-label">Description</label>
<textarea name="description" id="pDescription" class="form-control" rows="8">{{ $pack->description }}</textarea>
</div>
<div class="form-group">
<label for="pVersion" class="form-label">Version</label>
<input type="text" name="version" id="pVersion" class="form-control" value="{{ $pack->version }}" />
<p class="text-muted small">The version of this package, or the version of the files contained within the package.</p>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">Pack Configuration</h3>
</div>
<div class="box-body">
<div class="form-group">
<label for="pOptionId" class="form-label">Associated Option</label>
<select id="pOptionId" name="option_id" class="form-control">
@foreach($services as $service)
<optgroup label="{{ $service->name }}">
@foreach($service->options as $option)
<option value="{{ $option->id }}" {{ $pack->option_id !== $option->id ?: 'selected' }}>{{ $option->name }}</option>
@endforeach
</optgroup>
@endforeach
</select>
<p class="text-muted small">The option that this pack is assocaited with. Only servers that are assigned this option will be able to access this pack. This assigned option <em>cannot</em> be changed if servers are attached to this pack.</p>
</div>
<div class="form-group">
<div class="checkbox checkbox-primary no-margin-bottom">
<input id="pSelectable" name="selectable" type="checkbox" value="1" {{ ! $pack->selectable ?: 'checked' }}/>
<label for="pSelectable">
Selectable
</label>
</div>
<p class="text-muted small">Check this box if user should be able to select this pack to install on their servers.</p>
</div>
<div class="form-group">
<div class="checkbox checkbox-primary no-margin-bottom">
<input id="pVisible" name="visible" type="checkbox" value="1" {{ ! $pack->visible ?: 'checked' }}/>
<label for="pVisible">
Visible
</label>
</div>
<p class="text-muted small">Check this box if this pack is visible in the dropdown menu. If this pack is assigned to a server it will be visible regardless of this setting.</p>
</div>
<div class="form-group">
<div class="checkbox checkbox-warning no-margin-bottom">
<input id="pLocked" name="locked" type="checkbox" value="1" {{ ! $pack->locked ?: 'checked' }}/>
<label for="pLocked">
Locked
</label>
</div>
<p class="text-muted small">Check this box if servers assigned this pack should not be able to switch to a different pack.</p>
</div>
</div>
<div class="box-footer with-border">
{!! csrf_field() !!}
<button name="action" value="delete" class="btn btn-sm btn-danger pull-left muted muted-hover" type="submit"><i class="fa fa-trash-o"></i></button>
<button name="action" value="edit" class="btn btn-sm btn-primary pull-right" type="submit">Save</button>
</div>
</div>
</div>
</div>
</form>
<div class="row">
<div class="col-xs-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">Stored Files</h3>
</div>
<div class="box-body no-padding table-responsive">
<table class="table table-hover">
<tr>
<th>Name</th>
<th>SHA1 Hash</th>
<th>File Size</th>
</tr>
@foreach($pack->files() as $file)
<tr>
<td>{{ $file->name }}</td>
<td><code>{{ $file->hash }}</code></td>
<td>{{ $file->size }}</td>
</tr>
@endforeach
</table>
</div>
<div class="box-footer">
<p class="text-muted small">If you would like to modified the stored pack you will need to upload a new <code>archive.tar.gz</code> to the location defined below.</p>
<p class="text-muted small"><strong>Storage Location:</strong> <code>{{ storage_path('app/packs/' . $pack->uuid) }}</code></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">Servers Using This Pack</h3>
</div>
<div class="box-body no-padding table-responsive">
<table class="table table-hover">
<tr>
<th>ID</th>
<th>Server Name</th>
<th>Node</th>
<th>Owner</th>
</tr>
@foreach($pack->servers as $server)
<tr>
<td><code>{{ $server->uuidShort }}</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><a href="{{ route('admin.users.view', $server->user->id) }}">{{ $server->user->email }}</a></td>
</tr>
@endforeach
</table>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-6 col-md-5 col-md-offset-7 col-xs-offset-6">
<form action="{{ route('admin.packs.view.export', $pack->id) }}" method="POST">
{!! csrf_field() !!}
<button type="submit" class="btn btn-sm btn-success pull-right">Export</button>
</form>
<form action="{{ route('admin.packs.view.export', ['id' => $pack->id, 'files' => 'with-files']) }}" method="POST">
{!! csrf_field() !!}
<button type="submit" class="btn btn-sm pull-right muted muted-hover" style="margin-right:10px;">Export with Archive</button>
</form>
</div>
</div>
@endsection
@section('footer-scripts')
@parent
<script>
$('#pOptionId').select2();
</script>
@endsection

View File

@ -62,10 +62,7 @@
</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">
@ -91,7 +88,7 @@
</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>
<p class="no-margin text-muted small"><strong>Input Rules:</strong> <code>{{ $variable->rules }}</code></p>
</div>
</div>
</div>

View File

@ -0,0 +1,88 @@
{{-- 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')
Services &rarr; {{ $service->name }} &rarr; Functions
@endsection
@section('content-header')
<h1>{{ $service->name }}<small>Extend the default daemon functions using this service file.</small></h1>
<ol class="breadcrumb">
<li><a href="{{ route('admin.index') }}">Admin</a></li>
<li><a href="{{ route('admin.services') }}">Services</a></li>
<li><a href="{{ route('admin.services.view', $service->id) }}">{{ $service->name }}</a></li>
<li class="active">Functions</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.services.view', $service->id) }}">Overview</a></li>
<li class="active"><a href="{{ route('admin.services.view.functions', $service->id) }}">Functions</a></li>
</ul>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">Functions Control</h3>
</div>
<form action="{{ route('admin.services.view', $service->id) }}" method="POST">
<div class="box-body no-padding">
<div id="editor_index"style="height:500px">{{ $service->index_file }}</div>
<textarea name="index_file" class="hidden"></textarea>
</div>
<div class="box-footer">
{!! csrf_field() !!}
<input type="hidden" name="redirect_to" value="functions" />
<button type="submit" name="action" value="edit" class="btn btn-sm btn-success pull-right">Save File</button>
</div>
</form>
</div>
</div>
</div>
@endsection
@section('footer-scripts')
@parent
{!! Theme::js('js/vendor/ace/ace.js') !!}
{!! Theme::js('js/vendor/ace/ext-modelist.js') !!}
<script>
$(document).ready(function () {
const Editor = ace.edit('editor_index');
const Modelist = ace.require('ace/ext/modelist')
Editor.setTheme('ace/theme/chrome');
Editor.getSession().setMode('ace/mode/javascript');
Editor.getSession().setUseWrapMode(true);
Editor.setShowPrintMargin(false);
$('form').on('submit', function (e) {
$('textarea[name="index_file"]').val(Editor.getValue());
});
});
</script>
@endsection

View File

@ -0,0 +1,67 @@
{{-- 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')
Services
@endsection
@section('content-header')
<h1>Services<small>All services currently available on this system.</small></h1>
<ol class="breadcrumb">
<li><a href="{{ route('admin.index') }}">Admin</a></li>
<li class="active">Services</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">Configured Services</h3>
</div>
<div class="box-body table-responsive no-padding">
<table class="table table-hover">
<tr>
<th>Name</th>
<th>Description</th>
<th class="text-center">Options</th>
<th class="text-center">Packs</th>
<th class="text-center">Servers</th>
</tr>
@foreach($services as $service)
<tr>
<td class="middle"><a href="{{ route('admin.services.view', $service->id) }}">{{ $service->name }}</a></td>
<td class="col-xs-6 middle">{{ $service->description }}</td>
<td class="text-center middle"><code>{{ $service->options_count }}</code></td>
<td class="text-center middle"><code>{{ $service->packs_count }}</code></td>
<td class="text-center middle"><code>{{ $service->servers_count }}</code></td>
</tr>
@endforeach
</table>
</div>
<div class="box-footer">
<a href="{{ route('admin.services.new') }}"><button class="btn btn-primary btn-sm pull-right">Create Service</button></a>
</div>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,86 @@
{{-- 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 Service
@endsection
@section('content-header')
<h1>New Service<small>Configure a new service to deploy to all nodes.</small></h1>
<ol class="breadcrumb">
<li><a href="{{ route('admin.index') }}">Admin</a></li>
<li><a href="{{ route('admin.services') }}">Services</a></li>
<li class="active">New</li>
</ol>
@endsection
@section('content')
<form action="{{ route('admin.services.new') }}" method="POST">
<div class="row">
<div class="col-xs-6">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">New Service</h3>
</div>
<div class="box-body">
<div class="form-group">
<label class="control-label">Name</label>
<div>
<input type="text" name="name" class="form-control" value="{{ old('name') }}" />
<p class="text-muted"><small>This should be a descriptive category name that emcompasses all of the options within the service.</small></p>
</div>
</div>
<div class="form-group">
<label class="control-label">Description</label>
<div>
<textarea name="description" class="form-control" rows="6">{{ old('description') }}</textarea>
</div>
</div>
</div>
</div>
</div>
<div class="col-xs-6">
<div class="box">
<div class="box-body">
<div class="form-group">
<label class="control-label">Folder Name</label>
<div>
<input type="text" name="folder" class="form-control" value="{{ old('folder') }}" />
<p class="text-muted"><small>Services are downloaded by the daemon and stored in a folder using this name. The storage location is <code>/srv/daemon/services/{NAME}</code> by default.</small></p>
</div>
</div>
<div class="form-group">
<label class="control-label">Default Start Command</label>
<div>
<textarea name="startup" class="form-control" rows="2">{{ old('startup') }}</textarea>
<p class="text-muted"><small>The default start command to use when running options under this service. This command can be modified per-option and should include the executable to be called in the container.</small></p>
</div>
</div>
</div>
<div class="box-footer">
{!! csrf_field() !!}
<button type="input" class="btn btn-primary pull-right">Save Service</button>
</div>
</div>
</div>
</div>
</form>
@endsection

View File

@ -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')
Services &rarr; New Option
@endsection
@section('content-header')
<h1>New Option<small>Create a new service option to assign to servers.</small></h1>
<ol class="breadcrumb">
<li><a href="{{ route('admin.index') }}">Admin</a></li>
<li><a href="{{ route('admin.services') }}">Services</a></li>
<li class="active">New Service Option</li>
</ol>
@endsection
@section('content')
<form action="{{ route('admin.services.option.new') }}" method="POST">
<div class="row">
<div class="col-xs-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">Configuration</h3>
</div>
<div class="box-body">
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label for="pServiceId" class="form-label">Associated Service</label>
<select name="service_id" id="pServiceId">
@foreach($services as $service)
<option value="{{ $service->id }}">{{ $service->name }}</option>
@endforeach
</select>
</div>
<div class="form-group">
<label for="pName" class="form-label">Option Name</label>
<input type="text" id="pName" name="name" value="{{ old('name') }}" class="form-control" />
<p class="text-muted small">A simple, human-readable name to use as an identifier for this service.</p>
</div>
<div class="form-group">
<label for="pDescription" class="form-label">Description</label>
<textarea id="pDescription" name="description" class="form-control" rows="8">{{ old('description') }}</textarea>
<p class="text-muted small">A description of this service that will be displayed throughout the panel as needed.</p>
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label for="pTag" class="form-label">Option Tag</label>
<input type="text" id="pTag" name="tag" value="{{ old('tag') }}" class="form-control" />
<p class="text-muted small">This should be a unique identifer for this service option that is not used for any other service options.</p>
</div>
<div class="form-group">
<label for="pDockerImage" class="form-label">Docker Image</label>
<input type="text" id="pDockerImage" name="docker_image" value="{{ old('docker_image') }}" placeholder="quay.io/pterodactyl/service" class="form-control" />
<p class="text-muted small">The default docker image that should be used for new servers under this service option. This can be left blank to use the parent service's defined image, and can also be changed per-server.</p>
</div>
<div class="form-group">
<label for="pStartup" class="form-label">Startup Command</label>
<textarea id="pStartup" name="startup" class="form-control" rows="4">{{ old('startup') }}</textarea>
<p class="text-muted small">The default statup command that should be used for new servers under this service option. This can be left blank to use the parent service's startup, and can also be changed per-server.</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-xs-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">Process Management</h3>
</div>
<div class="box-body">
<div class="row">
<div class="col-xs-12">
<div class="alert alert-warning">
<p>All fields are required unless you select a seperate option from the 'Copy Settings From' dropdown, in which case fields may be left blank to use the values from that option.</p>
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label for="pConfigFrom" class="form-label">Copy Settings From</label>
<select name="config_from" id="pConfigFrom" class="form-control">
<option value="0">None</option>
</select>
<p class="text-muted small">If you would like to default to settings from another option select the option from the menu above.</p>
</div>
<div class="form-group">
<label for="pConfigStop" class="form-label">Stop Command</label>
<input type="text" id="pConfigStop" name="config_stop" class="form-control" value="{{ old('config_stop') }}" />
<p class="text-muted small">The command that should be sent to server processes to stop them gracefully. If you need to send a <code>SIGINT</code> you should enter <code>^C</code> here.</p>
</div>
<div class="form-group">
<label for="pConfigLogs" class="form-label">Log Configuration</label>
<textarea data-action="handle-tabs" id="pConfigLogs" name="config_logs" class="form-control" rows="6">{{ old('config_logs') }}</textarea>
<p class="text-muted small">This should be a JSON representation of where log files are stored, and wether or not the daemon should be creating custom logs.</p>
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label for="pConfigFiles" class="form-label">Configuration Files</label>
<textarea data-action="handle-tabs" id="pConfigFiles" name="config_files" class="form-control" rows="6">{{ old('config_files') }}</textarea>
<p class="text-muted small">This should be a JSON representation of configuration files to modify and what parts should be changed.</p>
</div>
<div class="form-group">
<label for="pConfigStartup" class="form-label">Start Configuration</label>
<textarea data-action="handle-tabs" id="pConfigStartup" name="config_startup" class="form-control" rows="6">{{ old('config_startup') }}</textarea>
<p class="text-muted small">This should be a JSON representation of what values the daemon should be looking for when booting a server to determine completion.</p>
</div>
</div>
</div>
<div class="box-footer">
{!! csrf_field() !!}
<button type="submit" class="btn btn-success btn-sm pull-right">Create Service</button>
</div>
</div>
</div>
</div>
</form>
@endsection
@section('footer-scripts')
@parent
{!! Theme::js('vendor/lodash/lodash.js') !!}
<script>
$(document).ready(function() {
$('#pServiceId').select2().change();
$('#pConfigFrom').select2();
});
$('#pServiceId').on('change', function (event) {
$('#pConfigFrom').html('<option value="0">None</option>').select2({
data: $.map(_.get(Pterodactyl.services, $(this).val() + '.options', []), function (item) {
return {
id: item.id,
text: item.name,
};
}),
});
});
$('textarea[data-action="handle-tabs"]').on('keydown', function(event) {
if (event.keyCode === 9) {
event.preventDefault();
var curPos = $(this)[0].selectionStart;
var prepend = $(this).val().substr(0, curPos);
var append = $(this).val().substr(curPos);
$(this).val(prepend + ' ' + append);
}
});
</script>
@endsection

View File

@ -0,0 +1,166 @@
{{-- 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')
Service Options &rarr; {{ $option->name }} &rarr; Variables
@endsection
@section('content-header')
<h1>{{ $option->name }}<small>Managing variables for this service option.</small></h1>
<ol class="breadcrumb">
<li><a href="{{ route('admin.index') }}">Admin</a></li>
<li><a href="{{ route('admin.services') }}">Services</a></li>
<li><a href="{{ route('admin.services.view', $option->service->id) }}">{{ $option->service->name }}</a></li>
<li><a href="{{ route('admin.services.option.view', $option->id) }}">{{ $option->name }}</a></li>
<li class="active">Variables</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.services.option.view', $option->id) }}">Configuration</a></li>
<li class="active"><a href="{{ route('admin.services.option.variables', $option->id) }}">Variables</a></li>
<li class="tab-success"><a href="#modal" data-toggle="modal" data-target="#newVariableModal">New Variable</a></li>
</ul>
</div>
</div>
</div>
<div class="row">
@foreach($option->variables as $variable)
<div class="col-sm-6">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ $variable->name }}</h3>
</div>
<form action="{{ route('admin.services.option.variables.edit', ['id' => $option->id, 'variable' => $variable->id]) }}" method="POST">
<div class="box-body">
<div class="form-group">
<label class="form-label">Name</label>
<input type="text" name="name" value="{{ $variable->name }}" class="form-control" />
</div>
<div class="form-group">
<label class="form-label">Description</label>
<textarea name="description" class="form-control" rows="3">{{ $variable->description }}</textarea>
</div>
<div class="row">
<div class="form-group col-md-6">
<label class="form-label">Environment Variable</label>
<input type="text" name="env_variable" value="{{ $variable->env_variable }}" class="form-control" />
</div>
<div class="form-group col-md-6">
<label class="form-label">Default Value</label>
<input type="text" name="default_value" value="{{ $variable->default_value }}" class="form-control" />
</div>
<div class="col-xs-12">
<p class="text-muted small">This variable can be accessed in the statup command by using <code>{{ $variable->env_variable }}</code>.</p>
</div>
</div>
<div class="form-group">
<label class="form-label">Permissions</label>
<select name="options[]" class="pOptions form-control" multiple>
<option value="user_viewable" {{ (! $variable->user_viewable) ?: 'selected' }}>Users Can View</option>
<option value="user_editable" {{ (! $variable->user_editable) ?: 'selected' }}>Users Can Edit</option>
</select>
</div>
<div class="form-group">
<label class="form-label">Input Rules</label>
<input type="text" name="rules" class="form-control" value="{{ $variable->rules }}" />
<p class="text-muted small">These rules are defined using standard Laravel Framework validation rules.</p>
</div>
</div>
<div class="box-footer">
{!! csrf_field() !!}
<button class="btn btn-sm btn-danger pull-left muted muted-hover" data-action="delete" name="action" value="delete" type="submit"><i class="fa fa-trash-o"></i></button>
<button class="btn btn-sm btn-primary pull-right" name="action" value="save" type="submit">Save</button>
</div>
</form>
</div>
</div>
@endforeach
</div>
<div class="modal fade" id="newVariableModal" 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">&times;</span></button>
<h4 class="modal-title">Create New Option Variable</h4>
</div>
<form action="{{ route('admin.services.option.variables', $option->id) }}" method="POST">
<div class="modal-body">
<div class="form-group">
<label class="form-label">Name</label>
<input type="text" name="name" class="form-control" />
</div>
<div class="form-group">
<label class="form-label">Description</label>
<textarea name="description" class="form-control" rows="3"></textarea>
</div>
<div class="row">
<div class="form-group col-md-6">
<label class="form-label">Environment Variable</label>
<input type="text" name="env_variable" class="form-control" />
</div>
<div class="form-group col-md-6">
<label class="form-label">Default Value</label>
<input type="text" name="default_value" class="form-control" />
</div>
<div class="col-xs-12">
<p class="text-muted small">This variable can be accessed in the statup command by entering <code>@{{environment variable value}}</code>.</p>
</div>
</div>
<div class="form-group">
<label class="form-label">Permissions</label>
<select name="options[]" class="pOptions form-control" multiple>
<option value="user_viewable">Users Can View</option>
<option value="user_editable">Users Can Edit</option>
</select>
</div>
<div class="form-group">
<label class="form-label">Input Rules</label>
<input type="text" name="rules" class="form-control" placeholder="required|string|max:20" />
<p class="text-muted small">These rules are defined using standard Laravel Framework validation rules.</p>
</div>
</div>
<div class="modal-footer">
{!! csrf_field() !!}
<button type="button" class="btn btn-default pull-left" data-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary">Create Variable</button>
</div>
</form>
</div>
</div>
</div>
@endsection
@section('footer-scripts')
@parent
<script>
$('.pOptions').select2();
$('[data-action="delete"]').on('mouseenter', function (event) {
$(this).find('i').html(' Delete Variable');
}).on('mouseleave', function (event) {
$(this).find('i').html('');
});
</script>
@endsection

View File

@ -0,0 +1,172 @@
{{-- 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')
Services &rarr; Option: {{ $option->name }}
@endsection
@section('content-header')
<h1>{{ $option->name }}<small>{{ str_limit($option->description, 50) }}</small></h1>
<ol class="breadcrumb">
<li><a href="{{ route('admin.index') }}">Admin</a></li>
<li><a href="{{ route('admin.services') }}">Services</a></li>
<li><a href="{{ route('admin.services.view', $option->service->id) }}">{{ $option->service->name }}</a></li>
<li class="active">{{ $option->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.services.option.view', $option->id) }}">Configuration</a></li>
<li><a href="{{ route('admin.services.option.variables', $option->id) }}">Variables</a></li>
</ul>
</div>
</div>
</div>
<form action="{{ route('admin.services.option.view', $option->id) }}" method="POST">
<div class="row">
<div class="col-xs-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">Configuration</h3>
</div>
<div class="box-body">
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label for="pName" class="form-label">Option Name</label>
<input type="text" id="pName" name="name" value="{{ $option->name }}" class="form-control" />
<p class="text-muted small">A simple, human-readable name to use as an identifier for this service.</p>
</div>
<div class="form-group">
<label for="pDescription" class="form-label">Description</label>
<textarea id="pDescription" name="description" class="form-control" rows="10">{{ $option->description }}</textarea>
<p class="text-muted small">A description of this service that will be displayed throughout the panel as needed.</p>
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label for="pTag" class="form-label">Option Tag</label>
<input type="text" id="pTag" name="tag" value="{{ $option->tag }}" class="form-control" />
<p class="text-muted small">This should be a unique identifer for this service option that is not used for any other service options.</p>
</div>
<div class="form-group">
<label for="pDockerImage" class="form-label">Docker Image</label>
<input type="text" id="pDockerImage" name="docker_image" value="{{ $option->docker_image }}" class="form-control" />
<p class="text-muted small">The default docker image that should be used for new servers under this service option. This can be left blank to use the parent service's defined image, and can also be changed per-server.</p>
</div>
<div class="form-group">
<label for="pStartup" class="form-label">Startup Command</label>
<textarea id="pStartup" name="startup" class="form-control" rows="4" placeholder="{{ $option->service->startup }}">{{ $option->startup }}</textarea>
<p class="text-muted small">The default statup command that should be used for new servers under this service option. This can be left blank to use the parent service's startup, and can also be changed per-server.</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-xs-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">Process Management</h3>
</div>
<div class="box-body">
<div class="row">
<div class="col-xs-12">
<div class="alert alert-warning">
<p>The following configuration options should not be edited unless you understand how this system works. If wrongly modified it is possible for the daemon to break.</p>
<p>All fields are required unless you select a seperate option from the 'Copy Settings From' dropdown, in which case fields may be left blank to use the values from that option.</p>
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label for="pConfigFrom" class="form-label">Copy Settings From</label>
<select name="config_from" id="pConfigFrom" class="form-control">
<option value="0">None</option>
@foreach($option->service->options as $o)
<option value="{{ $o->id }}" {{ ($option->config_from !== $o->id) ?: 'selected' }}>{{ $o->name }}</option>
@endforeach
</select>
<p class="text-muted small">If you would like to default to settings from another option select the option from the menu above.</p>
</div>
<div class="form-group">
<label for="pConfigStop" class="form-label">Stop Command</label>
<input type="text" id="pConfigStop" name="config_stop" class="form-control" value="{{ $option->config_stop }}" />
<p class="text-muted small">The command that should be sent to server processes to stop them gracefully. If you need to send a <code>SIGINT</code> you should enter <code>^C</code> here.</p>
</div>
<div class="form-group">
<label for="pConfigLogs" class="form-label">Log Configuration</label>
<textarea data-action="handle-tabs" id="pConfigLogs" name="config_logs" class="form-control" rows="6">{{ ! is_null($option->config_logs) ? json_encode(json_decode($option->config_logs), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) : '' }}</textarea>
<p class="text-muted small">This should be a JSON representation of where log files are stored, and wether or not the daemon should be creating custom logs.</p>
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label for="pConfigFiles" class="form-label">Configuration Files</label>
<textarea data-action="handle-tabs" id="pConfigFiles" name="config_files" class="form-control" rows="6">{{ ! is_null($option->config_files) ? json_encode(json_decode($option->config_files), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) : '' }}</textarea>
<p class="text-muted small">This should be a JSON representation of configuration files to modify and what parts should be changed.</p>
</div>
<div class="form-group">
<label for="pConfigStartup" class="form-label">Start Configuration</label>
<textarea data-action="handle-tabs" id="pConfigStartup" name="config_startup" class="form-control" rows="6">{{ ! is_null($option->config_startup) ? json_encode(json_decode($option->config_startup), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) : '' }}</textarea>
<p class="text-muted small">This should be a JSON representation of what values the daemon should be looking for when booting a server to determine completion.</p>
</div>
</div>
</div>
</div>
<div class="box-footer">
{!! csrf_field() !!}
<button id="deleteButton" type="submit" name="action" value="delete" class="btn btn-danger btn-sm muted muted-hover">
<i class="fa fa-trash-o"></i>
</button>
<button type="submit" name="action" value="edit" class="btn btn-primary btn-sm pull-right">Edit Service</button>
</div>
</div>
</div>
</div>
</form>
@endsection
@section('footer-scripts')
@parent
<script>
$('#pConfigFrom').select2();
$('#deleteButton').on('mouseenter', function (event) {
$(this).find('i').html(' Delete Option');
}).on('mouseleave', function (event) {
$(this).find('i').html('');
});
$('textarea[data-action="handle-tabs"]').on('keydown', function(event) {
if (event.keyCode === 9) {
event.preventDefault();
var curPos = $(this)[0].selectionStart;
var prepend = $(this).val().substr(0, curPos);
var append = $(this).val().substr(curPos);
$(this).val(prepend + ' ' + append);
}
});
</script>
@endsection

View File

@ -0,0 +1,135 @@
{{-- 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')
Services &rarr; {{ $service->name }}
@endsection
@section('content-header')
<h1>{{ $service->name }}<small>{{ str_limit($service->description, 50) }}</small></h1>
<ol class="breadcrumb">
<li><a href="{{ route('admin.index') }}">Admin</a></li>
<li><a href="{{ route('admin.services') }}">Services</a></li>
<li class="active">{{ $service->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.services.view', $service->id) }}">Overview</a></li>
<li><a href="{{ route('admin.services.view.functions', $service->id) }}">Functions</a></li>
</ul>
</div>
</div>
</div>
<form action="{{ route('admin.services.view', $service->id) }}" method="POST">
<div class="row">
<div class="col-xs-6">
<div class="box">
<div class="box-body">
<div class="form-group">
<label class="control-label">Name</label>
<div>
<input type="text" name="name" class="form-control" value="{{ $service->name }}" />
<p class="text-muted"><small>This should be a descriptive category name that emcompasses all of the options within the service.</small></p>
</div>
</div>
<div class="form-group">
<label class="control-label">Description</label>
<div>
<textarea name="description" class="form-control" rows="6">{{ $service->description }}</textarea>
</div>
</div>
</div>
</div>
</div>
<div class="col-xs-6">
<div class="box">
<div class="box-body">
<div class="form-group">
<label class="control-label">Folder Name</label>
<div>
<input type="text" name="folder" class="form-control" value="{{ $service->folder }}" />
<p class="text-muted"><small>Services are downloaded by the daemon and stored in a folder using this name. The storage location is <code>/srv/daemon/services/{NAME}</code> by default.</small></p>
</div>
</div>
<div class="form-group">
<label class="control-label">Default Start Command</label>
<div>
<textarea name="startup" class="form-control" rows="2">{{ $service->startup }}</textarea>
<p class="text-muted"><small>The default start command to use when running options under this service. This command can be modified per-option and should include the executable to be called in the container.</small></p>
</div>
</div>
</div>
<div class="box-footer">
{!! csrf_field() !!}
<button id="deleteButton" type="input" name="action" value="delete" class="btn btn-sm btn-danger muted muted-hover"><i class="fa fa-trash-o"></i></button>
<button type="input" class="btn btn-primary btn-sm pull-right">Edit Service</button>
</div>
</div>
</div>
</div>
</form>
<div class="row">
<div class="col-xs-12">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">Configured Options</h3>
</div>
<div class="box-body table-responsive no-padding">
<table class="table table-hover">
<tr>
<th class="col-sm-4 col-md-3">Name</th>
<th>Description</th>
<th>Tag</th>
<th class="text-center">Servers</th>
</tr>
@foreach($service->options as $option)
<tr>
<td><a href="{{ route('admin.services.option.view', $option->id) }}">{{ $option->name }}</a></td>
<td>{!! $option->description !!}</td>
<td><code>{{ $option->tag }}</code></td>
<td class="text-center">{{ $option->servers->count() }}</td>
</tr>
@endforeach
</table>
</div>
<div class="box-footer">
<a href="{{ route('admin.services.option.new') }}"><button class="btn btn-success btn-sm pull-right">New Service Option</button></a>
</div>
</div>
</div>
</div>
@endsection
@section('footer-scripts')
@parent
<script>
$('#deleteButton').on('mouseenter', function (event) {
$(this).find('i').html(' Delete Service');
}).on('mouseleave', function (event) {
$(this).find('i').html('');
});
</script>
@endsection

View File

@ -106,6 +106,17 @@
<i class="fa fa-users"></i> <span>Users</span>
</a>
</li>
<li class="header">SERVICE MANAGEMENT</li>
<li class="{{ ! starts_with(Route::currentRouteName(), 'admin.services') ?: 'active' }}">
<a href="{{ route('admin.services') }}">
<i class="fa fa-th-large"></i> <span>Services</span>
</a>
</li>
<li class="{{ ! starts_with(Route::currentRouteName(), 'admin.packs') ?: 'active' }}">
<a href="{{ route('admin.packs') }}">
<i class="fa fa-archive"></i> <span>Packs</span>
</a>
</li>
</ul>
</section>
</aside>

View File

@ -42,9 +42,8 @@
<h3 class="box-title">@lang('server.config.startup.command')</h3>
</div>
<div class="box-body">
<div class="input-group">
<span class="input-group-addon">{{ $service->executable }}</span>
<input type="text" class="form-control" readonly="readonly" value="{{ $processedStartup }}" />
<div class="form-group">
<input type="text" class="form-control" readonly value="{{ $processedStartup }}" />
</div>
</div>
@can('edit-startup', $server)
@ -56,35 +55,35 @@
</div>
</div>
@can('edit-startup', $server)
@foreach($variables as $variable)
@foreach($variables as $v)
<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>
<h3 class="box-title">{{ $v->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 }}"
<input
@if($v->user_can_edit)
name="env_{{ $v->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>
class="form-control" type="text" value="{{ old('env_' . $v->id, $v->variable_value) }}" />
<p class="small text-muted">{{ $v->variable->description }}</p>
<p class="no-margin">
@if($variable->required && $variable->user_editable)
@if($v->required && $v->user_can_edit)
<span class="label label-danger">@lang('strings.required')</span>
@elseif(! $variable->required && $variable->user_editable)
@elseif(! $v->required && $v->user_can_edit)
<span class="label label-default">@lang('strings.optional')</span>
@endif
@if(! $variable->user_editable)
@if(! $v->user_can_edit)
<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>
<p class="no-margin text-muted small"><strong>@lang('server.config.startup.startup_var'):</strong> <code>{{ $v->variable->env_variable }}</code></p>
<p class="no-margin text-muted small"><strong>@lang('server.config.startup.startup_regex'):</strong> <code>{{ $v->variable->rules }}</code></p>
</div>
</div>
</div>
@ -97,14 +96,4 @@
@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

View File

@ -1,687 +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 Server: {{ $server->name }} ({{ $server->uuidShort}})
@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">{{ $server->name }} ({{ $server->uuidShort}})</li>
</ul>
@if($server->suspended === 1 && !$server->trashed())
<div class="alert alert-warning">
This server is suspended and has no user access. Processes cannot be started and files cannot be modified. All API access is disabled unless using a master token.
</div>
@elseif($server->trashed())
<div class="alert alert-danger">
This server is marked for deletion <strong>{{ Carbon::parse($server->deleted_at)->addMinutes(env('APP_DELETE_MINUTES', 10))->diffForHumans() }}</strong>. If you want to cancel this action simply click the button below.
<br /><br />
<form action="{{ route('admin.servers.post.queuedDeletion', $server->id) }}" method="POST">
<button class="btn btn-sm btn-default" name="cancel" value="1">Cancel Deletion</button>
<button class="btn btn-sm btn-danger pull-right" name="force_delete" value="1"><strong>Force</strong> Delete</button>
<button class="btn btn-sm btn-danger pull-right" name="delete" style="margin-right:10px;" value="1">Delete</button>
{!! csrf_field() !!}
</form>
</div>
@endif
@if($server->installed === 0)
<div class="alert alert-warning">
This server is still running through the install process and is not avaliable for use just yet. This message will disappear once this process is completed.
</div>
@elseif($server->installed === 2)
<div class="alert alert-danger">
This server <strong>failed</strong> to install properly. You should delete it and try to create it again or check the daemon logs.
</div>
@endif
<ul class="nav nav-tabs tabs_with_panel" id="config_tabs">
<li class="active"><a href="#tab_about" data-toggle="tab">About</a></li>
@if($server->installed === 1)
<li><a href="#tab_details" data-toggle="tab">Details</a></li>
<li><a href="#tab_build" data-toggle="tab">Build Configuration</a></li>
<li><a href="#tab_startup" data-toggle="tab">Startup</a></li>
<li><a href="#tab_database" data-toggle="tab">Database</a></li>
@endif
@if($server->installed !== 2)
<li><a href="#tab_manage" data-toggle="tab">Manage</a></li>
@endif
@if(!$server->trashed())<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>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>Owner</td>
<td><a href="{{ route('admin.users.view', $server->owner_id) }}">{{ $server->user->email }}</a></td>
</tr>
<tr>
<td>Location</td>
<td><a href="{{ route('admin.locations') }}">{{ $server->node->location->short }}</a></td>
</tr>
<tr>
<td>Node</td>
<td><a href="{{ route('admin.nodes.view', $server->node_id) }}">{{ $server->node->name }}</a></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>
<tr>
<td>Installed</td>
<td>{!! ($server->installed === 1) ? '<span class="label label-success">Yes</span>' : '<span class="label label-danger">No</span>' !!}</td>
</tr>
<tr>
<td>Suspended</td>
<td>{!! ($server->suspended === 1) ? '<span class="label label-warning">Suspended</span>' : '<span class="label label-success">No</span>' !!}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
@if($server->installed === 1)
<div class="tab-pane" id="tab_details">
<div class="panel panel-default">
<div class="panel-heading"></div>
<div class="panel-body">
<form method="POST" action="/admin/servers/view/{{ $server->id }}/details">
<div class="form-group {{ $errors->has('name') ? 'has-error' : '' }}">
<label for="name" class="control-label">Server Name</label>
<div>
<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).</small></p>
</div>
</div>
<div class="form-group {{ $errors->has('owner') ? 'has-error' : '' }}">
<label for="name" class="control-label">Server Owner</label>
<div>
<input type="text" name="owner" value="{{ old('owner', $server->user->email) }}" class="form-control" />
<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.</small></p>
</div>
</div>
<div class="form-group">
<label for="name" class="control-label">Daemon Secret Token</label>
<div>
<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.</small></p>
</div>
</div>
<div class="form-group">
<div>
<input type="checkbox" name="reset_token"/> Yes, Reset Daemon Token
<p class="text-muted"><small>Resetting this token will cause any requests using the old token to fail.</small></p>
</div>
</div>
<div class="form-group">
{!! csrf_field() !!}
<input type="submit" class="btn btn-sm btn-primary" value="Update Details" />
</div>
</form>
</div>
<div class="panel-heading" style="border-top: 1px solid #ddd;"></div>
<div class="panel-body">
<form action="{{ route('admin.servers.post.container', $server->id) }}" method="POST">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="name" class="control-label">Docker Container Image</label>
<div>
<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->docker_image }}</code>.</small></p>
</div>
</div>
</div>
<div class="col-md-6 text-center">
<div class="form-group">
<label for="name" class="control-label">&nbsp;</label>
<div>
{!! csrf_field() !!}
<input type="submit" class="btn btn-sm btn-primary" value="Update Docker Image" />
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="tab-pane" id="tab_build">
<div class="panel panel-default">
<div class="panel-heading"></div>
<div class="panel-body">
<div class="row">
<div class="col-md-12">
<div class="alert alert-info">
After editing any of the options below you will need to restart the server for changes to take effect. If the server is currently off, you just need to start it and the container will be rebuilt with the new settings.
</div>
</div>
</div>
<form action="/admin/servers/view/{{ $server->id }}/build" method="POST">
<div class="row">
<div class="col-md-6 form-group {{ $errors->has('memory') ? 'has-error' : '' }}">
<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>
</div>
<div class="col-md-6 form-group {{ $errors->has('swap') ? 'has-error' : '' }}">
<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.</small></p>
</div>
</div>
<div class="row">
<div class="col-md-6 form-group {{ $errors->has('cpu') ? 'has-error' : '' }}">
<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.</small></p>
</div>
<div class="col-md-6 form-group {{ $errors->has('io') ? 'has-error' : '' }}">
<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>.</small></p>
</div>
</div>
<hr />
<div class="row">
<div class="col-md-12">
<div class="alert alert-info">
Additional IPs and Ports can be assigned to this server for use by plugins or other software. The game port is what will show up for the user to use to connect to thier server, and what their configuration files will be forced to use for binding.
</div>
</div>
<div class="col-md-6 form-group">
<label for="default" class="control-label">Game Port</label>
@foreach ($assigned as $assignment)
<div class="input-group" style="margin:5px auto;">
<span class="input-group-addon">
<input type="radio"
@if($assignment->id === $server->allocation_id) checked="checked" @endif
name="default" value="{{ $assignment->ip }}:{{ $assignment->port }}"/>
</span>
<input type="text" class="form-control" value="{{ $assignment->alias }}:{{ $assignment->port }}"
@if($assignment->has_alias)
data-toggle="tooltip" data-placement="left" title="{{ $assignment->ip }}:{{ $assignment->port }}"
@endif
/>
</div>
@endforeach
</div>
<div class="col-md-6">
<div class="row">
<div class="col-md-12 form-group">
<label for="add_additional" class="control-label">Assign Additional Ports</label>
<div>
<select name="add_additional[]" class="form-control" multiple>
@foreach ($unassigned as $assignment)
<option value="{{ $assignment->ip }}:{{ $assignment->port }}">{{ $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. For example, you <strong>cannot</strong> assign both <code>192.168.0.5:25565</code> and <code>192.168.10.5:25565</code> to the same server.</small></p>
</div>
</div>
<div class="row">
<div class="col-md-12 form-group">
<label for="remove_additional" class="control-label">Remove Additional Ports</label>
<div>
<select name="remove_additional[]" class="form-control" multiple>
@foreach ($assigned as $assignment)
<option value="{{ $assignment->ip }}:{{ $assignment->port }}" @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 above and delete it down here.</small></p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
{!! csrf_field() !!}
<input type="submit" class="btn btn-sm btn-primary" value="Update Build Configuration" />
</div>
</div>
</form>
</div>
</div>
</div>
<div class="tab-pane" id="tab_startup">
<form action="{{ route('admin.servers.post.startup', $server->id) }}" method="POST">
<div class="panel panel-default">
<div class="panel-heading"></div>
<div class="panel-body">
<div class="row">
<div class="col-md-12">
<div class="alert alert-info">Changing any of the values below will require a restart for them to take effect.</div>
<label class="control-label">Server Startup Command</label>
<div class="input-group">
<span class="input-group-addon">{{ $server->option->display_executable }}</span>
<input type="text" class="form-control" name="startup" value="{{ old('startup', $server->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>
<div class="panel-heading" style="border-top: 1px solid #ddd;"></div>
<div class="panel-body">
<div class="row">
@foreach($server->option->variables as $variable)
<div class="form-group col-md-6">
<label class="control-label">
@if($variable->required)<span class="label label-primary">Required</span> @endif
@if(! $variable->user_viewable)<span data-toggle="tooltip" data-placement="top" title="Not Visible to Users" class="label label-danger"><i class="fa fa-eye-slash"></i></span> @endif
@if(! $variable->user_editable)<span data-toggle="tooltip" data-placement="top" title="Not Editable by Users" class="label label-danger"><i class="fa fa-edit"></i></span> @endif
{{ $variable->name }}
</label>
<div>
<input type="text" name="{{ $variable->env_variable }}" class="form-control" value="{{ old($variable->env_variable, (! $variable->server_value) ? $variable->default_value : $variable->server_value) }}" data-action="matchRegex" data-regex="{{ $variable->regex }}" />
</div>
<p class="text-muted"><small>{!! $variable->description !!}<br />Regex: <code>{{ $variable->regex }}</code><br />Access as: <code>&#123;&#123;{{ $variable->env_variable }}&#125;&#125;</code></small></p>
</div>
@endforeach
</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-primary btn-sm" value="Update Startup Arguments" />
</div>
</div>
</div>
</div>
</form>
</div>
<div class="tab-pane" id="tab_database">
<div class="panel panel-default">
<div class="panel-heading"></div>
<div class="panel-body">
<h4 class="nopad">Add New Database</h4>
<form action="{{ route('admin.servers.database', $server->id) }}" method="post">
<div class="row">
<div class="form-group col-md-6">
<label class="control-label">Database Name:</label>
<div class="input-group">
<div class="input-group-addon">s{{ $server->id }}_</div>
<input type="text" name="database" value="{{ old('database') }}" class="form-control">
</div>
</div>
<div class="form-group col-md-6">
<label class="control-label">Connections From:</label>
<div>
<input type="text" name="remote" value="{{ old('remote', '%') }}" class="form-control">
</div>
<p class="text-muted"><small>Which IP to allow connections from. Standard MySQL wildcard notation allowed (e.g. <code>192.168.%.%</code>).</small></p>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<label class="control-label">Database Server:</label>
<select name="db_server" class="form-control">
@foreach($db_servers as $dbs)
<option value="{{ $dbs->id }}" @if($dbs->linked_node === $server->node_id)selected="selected"@endif>{{ $dbs->name }} ({{ $dbs->host }}:{{ $dbs->port }})</option>
@endforeach
</select>
</div>
<div class="col-xs-6">
<label class="control-label">&nbsp;</label>
<div>
{!! csrf_field() !!}
<input type="submit" value="Create New Database &rarr;" class="btn btn-sm btn-primary pull-right">
</div>
</div>
</div>
</form>
</div>
@if(count($server->databases) > 0)
<div class="panel-heading" style="border-top: 1px solid #ddd;"></div>
<div class="panel-body">
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>Database</th>
<th>User (Connections From)</th>
<th>Password</th>
<th>DB Server</th>
<th></th>
</th>
</thead>
<tbody>
@foreach($server->databases as $database)
<tr>
<td>{{ $database->database }}</td>
<td>{{ $database->username }} ({{ $database->remote }})</td>
<td><code>{{ Crypt::decrypt($database->password) }}</code> <a href="#" data-action="reset-database-password" data-id="{{ $database->id }}"><i class="fa fa-refresh pull-right"></i></a></td>
<td><code>{{ $database->host->host }}:{{ $database->host->port }}</code></td>
<td class="text-center"><a href="#delete" data-action="delete_database" data-database="{{ $database->id }}" class="text-danger"><i class="fa fa-trash-o"></i></a></td>
</tr>
@endforeach
</tbody>
</table>
</div>
@endif
</div>
</div>
@endif
@if($server->installed !== 2)
<div class="tab-pane" id="tab_manage">
<div class="panel panel-default">
<div class="panel-heading"></div>
<div class="panel-body">
<div class="row">
<div class="col-md-4 text-center">
<a href="/server/{{ $server->uuidShort }}/" target="_blank">
<button type="submit" class="btn btn-sm btn-primary">Manage Server</button>
</a>
</div>
<div class="col-md-8">
<p>This will take you to the server management page that users normally see and allow you to manage server files as well as check the console and data usage.</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-md-4 text-center">
<form action="/admin/servers/view/{{ $server->id }}/installed" method="POST">
{!! csrf_field() !!}
<button type="submit" class="btn btn-sm btn-primary">Toggle Install Status</button>
</form>
</div>
<div class="col-md-8">
<p>This will toggle the install status for the server.</p>
<div class="alert alert-warning">If you have just created this server it is ill advised to perform this action as the daemon will contact the panel when finished which could cause the install status to be wrongly set.</div>
</div>
</div>
</div>
@if($server->installed === 1)
<div class="panel-heading" style="border-top: 1px solid #ddd;"></div>
<div class="panel-body">
<div class="row">
<div class="col-md-4 text-center">
<form action="/admin/servers/view/{{ $server->id }}/rebuild" method="POST">
{!! csrf_field() !!}
<button type="submit" class="btn btn-sm btn-primary">Rebuild Server Container</button>
</form>
</div>
<div class="col-md-8">
<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 class="alert alert-info">A rebuild will automatically occur whenever you edit build configuration settings for the server.</div>
</div>
</div>
</div>
@endif
<div class="panel-heading" style="border-top: 1px solid #ddd;"></div>
<div class="panel-body">
<div class="row">
@if($server->suspended === 0)
<div class="col-md-4 text-center">
<form action="/admin/servers/view/{{ $server->id }}/suspend" method="POST">
{!! csrf_field() !!}
<button type="submit" class="btn btn-sm btn-warning">Suspend Server</button>
</form>
</div>
<div class="col-md-8">
<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>
@else
<div class="col-md-4 text-center">
<form action="/admin/servers/view/{{ $server->id }}/unsuspend" method="POST">
{!! csrf_field() !!}
<button type="submit" class="btn btn-sm btn-success">Unsuspend Server</button>
</form>
</div>
<div class="col-md-8">
<p>This will unsuspend the server and restore normal user access.</p>
</div>
@endif
</div>
</div>
</div>
</div>
@endif
<div class="tab-pane" id="tab_delete">
<div class="panel panel-default">
<div class="panel-heading"></div>
@if($server->installed === 1)
<div class="panel-body">
<div class="row">
<div class="col-md-4 text-center">
<form action="/admin/servers/view/{{ $server->id }}" method="POST" data-attr="deleteServer">
{!! csrf_field() !!}
{!! method_field('DELETE') !!}
<button type="submit" class="btn btn-sm btn-danger">Delete Server</button>
</form>
</div>
<div class="col-md-8">
<div class="alert alert-danger">Deleting a server is an irreversible action. <strong>All data will be immediately removed relating to this server.</strong></div>
</div>
</div>
</div>
<div class="panel-heading" style="border-top: 1px solid #ddd;"></div>
@endif
<div class="panel-body">
<div class="row">
<div class="col-md-4 text-center">
<form action="/admin/servers/view/{{ $server->id }}/force" method="POST" data-attr="deleteServer">
{!! csrf_field() !!}
{!! method_field('DELETE') !!}
<button type="submit" class="btn btn-sm btn-danger">Force Delete Server</button>
</form>
</div>
<div class="col-md-8">
<div class="alert alert-danger">This is the same as deleting a server, however, if an error is returned by the daemon it is ignored and the server is still removed from the panel.</strong></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
$(document).ready(function () {
$('[data-toggle="tooltip"]').tooltip();
$('#sidebar_links').find("a[href='/admin/servers']").addClass('active');
(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);
})
})();
$('input[name="default"]').on('change', function (event) {
$('select[name="remove_additional[]"]').find('option:disabled').prop('disabled', false);
$('select[name="remove_additional[]"]').find('option[value="' + $(this).val() + '"]').prop('disabled', true).prop('selected', false);
});
$('[data-action="matchRegex"]').keyup(function (event) {
if (!$(this).data('regex')) return;
var input = $(this).val();
var regex = new RegExp(escapeRegExp($(this).data('regex')));
console.log(regex);
if (!regex.test(input)) {
$(this).parent().parent().removeClass('has-success').addClass('has-error');
} else {
$(this).parent().parent().removeClass('has-error').addClass('has-success');
}
});
$('form[data-attr="deleteServer"]').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();
});
});
$('[data-action="delete_database"]').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
}, function () {
$.ajax({
method: 'DELETE',
url: '{{ route('admin.databases') }}/delete/' + self.data('database'),
headers: {
'X-CSRF-TOKEN': '{{ csrf_token() }}'
}
}).done(function () {
self.parent().parent().slideUp();
swal({
title: '',
type: 'success',
text: 'Successfully deleted this database.'
});
}).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-database-password"]').click(function (e) {
e.preventDefault();
var block = $(this);
$(this).find('i').addClass('fa-spin');
$.ajax({
type: 'POST',
url: '{{ route('server.ajax.reset-database-password', $server->uuidShort) }}',
headers: {
'X-CSRF-TOKEN': '{{ csrf_token() }}'
},
data: {
'database': $(this).data('id')
}
}).done(function (data) {
block.parent().find('code').html(data);
}).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.find('i').removeClass('fa-spin');
});
});
});
</script>
@endsection

View File

@ -1,180 +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')
Manage Service Configuration
@endsection
@section('content')
<div class="col-md-12">
<ul class="breadcrumb">
<li><a href="/admin">Admin Control</a></li>
<li><a href="/admin/services">Services</a></li>
<li><a href="{{ route('admin.services.service', $service->id) }}">{{ $service->name }}</a></li>
<li class="active">Configuration</li>
</ul>
<h3 class="nopad">Service Configuration</h3><hr />
<ul class="nav nav-tabs tabs_with_panel" id="config_tabs">
<li class="active"><a href="#tab_main" data-toggle="tab">main.json</a></li>
<li><a href="#tab_index" data-toggle="tab">index.js</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="tab_main">
<div class="panel panel-default">
<div class="panel-heading"></div>
<div class="panel-body" style="padding-top:0;">
<div class="row" style="border-bottom:1px solid #ccc;">
<div class="col-md-12" style="margin:0; padding:0;">
<div id="editor_json" style="height:500px;">{{ $contents['json'] }}</div>
</div>
</div>
<div class="row" style="margin-top:15px;">
<div class="col-md-12">
<button type="submit" id="save_json" class="btn btn-sm btn-success">Save Configuration</button>
</div>
</div>
</div>
</div>
</div>
<div class="tab-pane" id="tab_index">
<div class="panel panel-default">
<div class="panel-heading"></div>
<div class="panel-body" style="padding-top:0;">
<div class="row" style="border-bottom:1px solid #ccc;">
<div class="col-md-12" style="margin:0; padding:0;">
<div id="editor_index" style="height:500px;">{{ $contents['index'] }}</div>
</div>
</div>
<div class="row" style="margin-top:15px;">
<div class="col-md-12">
<button type="submit" id="save_index" class="btn btn-sm btn-success">Save Scripting</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{!! Theme::js('js/vendor/ace/ace.js') !!}
{!! Theme::js('js/vendor/ace/ext-modelist.js') !!}
<script>
$(document).ready(function () {
$('#sidebar_links').find("a[href='/admin/services']").addClass('active');
const JsonEditor = ace.edit('editor_json');
const IndexEditor = ace.edit('editor_index');
const Modelist = ace.require('ace/ext/modelist')
JsonEditor.setTheme('ace/theme/chrome');
JsonEditor.getSession().setMode('ace/mode/json');
JsonEditor.getSession().setUseWrapMode(true);
JsonEditor.setShowPrintMargin(false);
IndexEditor.setTheme('ace/theme/chrome');
IndexEditor.getSession().setMode('ace/mode/javascript');
IndexEditor.getSession().setUseWrapMode(true);
IndexEditor.setShowPrintMargin(false);
JsonEditor.commands.addCommand({
name: 'save',
bindKey: {win: 'Ctrl-S', mac: 'Command-S'},
exec: function(editor) {
saveConfig();
},
readOnly: false
});
IndexEditor.commands.addCommand({
name: 'save',
bindKey: {win: 'Ctrl-S', mac: 'Command-S'},
exec: function(editor) {
saveIndex();
},
readOnly: false
});
$('#save_json').on('click', function (e) {
e.preventDefault();
saveConfig();
});
$('#save_index').on('click', function (e) {
e.preventDefault();
saveIndex();
});
function saveConfig() {
$('#save_json').append(' <i class="fa fa-spinner fa fa-spin"></i>').addClass('disabled');
$.ajax({
type: 'POST',
url: '{{ route('admin.services.service.config', $service->id) }}',
headers: { 'X-CSRF-Token': '{{ csrf_token() }}' },
data: {
file: 'main',
contents: JsonEditor.getValue()
}
}).done(function (data) {
$.notify({
message: 'Service configuration file has been saved successfully.'
}, {
type: 'success'
});
}).fail(function (jqXHR) {
$.notify({
message: jqXHR.responseText
}, {
type: 'danger'
});
}).always(function () {
$('#save_json').html('Save Configuration').removeClass('disabled');
});
}
function saveIndex() {
$('#save_json').append(' <i class="fa fa-spinner fa fa-spin"></i>').addClass('disabled');
$.ajax({
type: 'POST',
url: '{{ route('admin.services.service.config', $service->id) }}',
headers: { 'X-CSRF-Token': '{{ csrf_token() }}' },
data: {
file: 'index',
contents: IndexEditor.getValue()
}
}).done(function (data) {
$.notify({
message: 'Service scripting file has been saved successfully.'
}, {
type: 'success'
});
}).fail(function (jqXHR) {
$.notify({
message: jqXHR.responseText
}, {
type: 'danger'
});
}).always(function () {
$('#save_json').html('Save Scripting').removeClass('disabled');
});
}
});
</script>
@endsection

View File

@ -1,65 +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')
Manage Services
@endsection
@section('content')
<div class="col-md-12">
<ul class="breadcrumb">
<li><a href="/admin">Admin Control</a></li>
<li class="active">Services</li>
</ul>
<h3 class="nopad">Server Services</h3><hr />
<table class="table table-bordered table-hover">
<thead>
<tr>
<th class="col-md-3">Service Type</th>
<th>Description</th>
<th class="text-center">Servers</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach ($services as $service)
<tr>
<td><a href="{{ route('admin.services.service', $service->id) }}">{{ $service->name }}</a></td>
<td>{!! $service->description !!}</td>
<td class="text-center">{{ $service->servers_count }}</td>
<td class="text-center align-middle"><a href="{{ route('admin.services.service.config', $service->id) }}"><button class="btn btn-xxs btn-primary"><i class="fa fa-wrench"></i> Configure</button></a></td>
</tr>
@endforeach
<tr>
<td></td>
<td></td>
<td></td>
<td class="text-center"><a href="{{ route('admin.services.new') }}"><i class="fa fa-plus"></i></a></td>
</tr>
</tbody>
</table>
</div>
<script>
$(document).ready(function () {
$('#sidebar_links').find("a[href='/admin/services']").addClass('active');
});
</script>
@endsection

View File

@ -1,95 +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')
New Service
@endsection
@section('content')
<div class="col-md-12">
<ul class="breadcrumb">
<li><a href="/admin">Admin Control</a></li>
<li><a href="{{ route('admin.services') }}">Services</a></li>
<li class="active">New Service</li>
</ul>
<h3 class="nopad">Add New Service</h3><hr />
<form action="{{ route('admin.services.new') }}" method="POST">
<div class="row">
<div class="col-md-6 form-group">
<label class="control-label">Service Name:</label>
<div>
<input type="text" name="name" class="form-control" value="{{ old('name') }}" />
<p class="text-muted"><small>This should be a descriptive category name that emcompasses all of the options within the service.</small></p>
</div>
</div>
<div class="col-md-6 form-group">
<label class="control-label">Service Description:</label>
<div>
<textarea name="description" class="form-control" rows="4">{{ old('description') }}</textarea>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 form-group">
<label class="control-label">Service Configuration File:</label>
<div class="input-group">
<span class="input-group-addon">/src/services/</span>
<input type="text" name="file" class="form-control" value="{{ old('file') }}" />
<span class="input-group-addon">/index.js</span>
</div>
<p class="text-muted"><small>This should be a unique alpha-numeric <code>(a-z)</code> name used to identify the service.</small></p>
</div>
<div class="col-md-6 form-group">
<label class="control-label">Display Executable:</label>
<div>
<input type="text" name="executable" class="form-control" value="{{ old('executable') }}" />
</div>
<p class="text-muted"><small>Changing this has no effect on operation of the daemon, it is simply used for display purposes on the panel. This can be changed per-option.</small></p>
</div>
</div>
<div class="row">
<div class="col-md-12 form-group">
<label class="control-label">Default Startup:</label>
<div class="input-group">
<span class="input-group-addon" id="disp_exec"></span>
<input type="text" name="startup" class="form-control" value="{{ old('startup') }}" />
</div>
<p class="text-muted"><small>This is the default startup that will be used for all servers created using this service. This can be changed per-option.</small></p>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="alert alert-info">You will be able to add service options and variables once the service is created.</div>
{!! csrf_field() !!}
<input type="submit" class="btn btn-sm btn-primary" value="Add New Service" />
</div>
</div>
</form>
</div>
<script>
$(document).ready(function () {
$('#sidebar_links').find("a[href='/admin/services/new']").addClass('active');
$('input[name="executable"]').on('keyup', function() {
$("#disp_exec").html(escape($(this).val()));
});
});
</script>
@endsection

View File

@ -1,98 +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')
New Service Option for {{ $service->name }}
@endsection
@section('content')
<div class="col-md-12">
<ul class="breadcrumb">
<li><a href="/admin">Admin Control</a></li>
<li><a href="/admin/services">Services</a></li>
<li><a href="{{ route('admin.services.service', $service->id) }}">{{ $service->name }}</a></li>
<li class="active">New Service Option</li>
</ul>
<h3>Service Option Settings</h3><hr />
<form action="{{ route('admin.services.option.new', $service->id) }}" method="POST">
<div class="row">
<div class="col-md-6 form-group">
<label class="control-label">Name:</label>
<div>
<input type="text" name="name" value="{{ old('name') }}" class="form-control" />
</div>
</div>
<div class="col-md-6 form-group">
<label class="control-label">Description:</label>
<div>
<textarea name="description" class="form-control" rows="3">{{ old('description') }}</textarea>
</div>
</div>
</div>
<div class="row">
<div class="col-md-3 form-group">
<label class="control-label">Tag:</label>
<div>
<input type="text" name="tag" value="{{ old('tag') }}" class="form-control" />
</div>
</div>
<div class="col-md-3 form-group">
<label class="control-label">Executable:</label>
<div>
<input type="text" name="executable" value="{{ old('executable') }}" class="form-control" />
<p class="text-muted"><small>Leave blank to use parent executable.</small></p>
</div>
</div>
<div class="col-md-6 form-group">
<label class="control-label">Docker Image:</label>
<div>
<input type="text" name="docker_image" value="{{ old('docker_image') }}" class="form-control" />
</div>
</div>
</div>
<div class="row">
<div class="col-md-12 form-group">
<label class="control-label">Default Startup Command:</label>
<div>
<input type="text" name="startup" value="{{ old('startup') }}" class="form-control" />
<p class="text-muted"><small>To use the default startup of the parent service simply leave this field blank.</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="Create Service Option" />
</div>
</div>
</div>
</form>
</div>
<script>
$(document).ready(function () {
$('#sidebar_links').find("a[href='/admin/services']").addClass('active');
$('#env_var').on('keyup', function () {
$(this).parent().find('code').html('&#123;&#123;' + escape($(this).val()) + '&#125;&#125;');
});
});
</script>
@endsection

View File

@ -1,122 +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')
New Variable for {{ $option->name }}
@endsection
@section('content')
<div class="col-md-12">
<ul class="breadcrumb">
<li><a href="/admin">Admin Control</a></li>
<li><a href="/admin/services">Services</a></li>
<li><a href="{{ route('admin.services.service', $option->service->id) }}">{{ $option->service->name }}</a></li>
<li><a href="{{ route('admin.services.option', [$option->service->id, $option->id]) }}">{{ $option->name }}</a></li>
<li class="active">New Variable</li>
</ul>
<h3>New Option Variable</h3><hr />
<form action="{{ route('admin.services.option.variable.new', [$option->service->id, $option->id]) }}" method="POST">
<div class="well">
<div class="row">
<div class="col-md-12 form-group">
<label class="control-label">Variable Name:</label>
<div>
<input type="text" name="name" class="form-control" value="{{ old('name') }}" />
</div>
</div>
</div>
<div class="row">
<div class="col-md-12 form-group">
<label class="control-label">Variable Description:</label>
<div>
<textarea name="description" class="form-control" rows="4">{{ old('description') }}</textarea>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12 form-group">
<label class="control-label">Regex:</label>
<div>
<input type="text" name="regex" class="form-control" value="{{ old('regex') }}" />
<p class="text-muted"><small>Regex code to use when verifying the contents of the field.</small></p>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 form-group">
<label class="control-label">Environment Variable:</label>
<div>
<input type="text" name="env_variable" id="env_var" class="form-control" value="{{ old('env_variable') }}" />
<p class="text-muted"><small>Accessed in startup by using <code>&#123;&#123;&#125;&#125;</code> parameter.</small></p>
</div>
</div>
<div class="col-md-6 form-group">
<label class="control-label">Default Value:</label>
<div>
<input type="text" name="default_value" class="form-control" value="{{ old('default_value') }}" />
<p class="text-muted"><small>The default value to use for this field.</small></p>
</div>
</div>
</div>
<div class="row fuelux">
<div class="col-md-4">
<div class="checkbox highlight">
<label class="checkbox-custom highlight" data-initialize="checkbox">
<input class="sr-only" name="user_viewable" type="checkbox" value="1" @if((int) old('user_viewable') === 1)checked="checked"@endif> <strong>User Viewable</strong>
<p class="text-muted"><small>Can users view this variable?</small><p>
</label>
</div>
</div>
<div class="col-md-4">
<div class="checkbox highlight">
<label class="checkbox-custom highlight" data-initialize="checkbox">
<input class="sr-only" name="user_editable" type="checkbox" value="1" @if((int) old('user_editable') === 1)checked="checked"@endif> <strong>User Editable</strong>
<p class="text-muted"><small>Can users edit this variable?</small><p>
</label>
</div>
</div>
<div class="col-md-4">
<div class="checkbox highlight">
<label class="checkbox-custom highlight" data-initialize="checkbox">
<input class="sr-only" name="required" type="checkbox" value="1" @if((int) old('required') === 1)checked="checked"@endif> <strong>Required</strong>
<p class="text-muted"><small>This this variable required?</small><p>
</label>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
{!! csrf_field() !!}
<input type="submit" class="btn btn-sm btn-primary" value="Add Variable" />
</div>
</div>
</div>
</form>
</div>
<script>
$(document).ready(function () {
$('#sidebar_links').find("a[href='/admin/services']").addClass('active');
$('#env_var').on('keyup', function () {
$(this).parent().find('code').html('&#123;&#123;' + escape($(this).val()) + '&#125;&#125;');
});
});
</script>
@endsection

View File

@ -1,211 +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')
Manage Service Option {{ $option->name }}
@endsection
@section('content')
<div class="col-md-12">
<ul class="breadcrumb">
<li><a href="/admin">Admin Control</a></li>
<li><a href="/admin/services">Services</a></li>
<li><a href="{{ route('admin.services.service', $option->service->id) }}">{{ $option->service->name }}</a></li>
<li class="active">{{ $option->name }}</li>
</ul>
<div class="alert alert-warning"><strong>Warning!</strong> This page contains advanced settings that the panel and daemon use to control servers. Modifying information on this page is not recommended unless you are absolutely sure of what you are doing.</div>
<h3>Settings</h3><hr />
<form action="{{ route('admin.services.option', [$option->service->id, $option->id]) }}" method="POST">
<div class="row">
<div class="col-md-6 form-group">
<label class="control-label">Name:</label>
<div>
<input type="text" name="name" value="{{ old('name', $option->name) }}" class="form-control" />
</div>
</div>
<div class="col-md-6 form-group">
<label class="control-label">Description:</label>
<div>
<textarea name="description" class="form-control" rows="3">{{ old('description', $option->description) }}</textarea>
</div>
</div>
</div>
<div class="row">
<div class="col-md-3 form-group">
<label class="control-label">Tag:</label>
<div>
<input type="text" name="tag" value="{{ old('tag', $option->tag) }}" class="form-control" />
</div>
</div>
<div class="col-md-3 form-group">
<label class="control-label">Executable:</label>
<div>
<input type="text" name="executable" value="{{ old('executable', $option->executable) }}" class="form-control" />
<p class="text-muted"><small>Leave blank to use parent executable.</small></p>
</div>
</div>
<div class="col-md-6 form-group">
<label class="control-label">Docker Image:</label>
<div>
<input type="text" name="docker_image" value="{{ old('docker_image', $option->docker_image) }}" class="form-control" />
<p class="text-muted"><small>Changing the docker image will only effect servers created or modified after this point.</small></p>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12 form-group">
<label class="control-label">Default Startup Command:</label>
<div>
<input type="text" name="startup" value="{{ old('startup', $option->startup) }}" placeholder="{{ $option->service->startup }}" class="form-control" />
<p class="text-muted"><small>To use the default startup of the parent service simply leave this field blank.</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="Update Service Option" />
</div>
</div>
</div>
</form>
<h3>Variables <small><a href="{{ route('admin.services.option.variable.new', [$option->service->id, $option->id]) }}"><i class="fa fa-plus"></i></a></small></h3><hr />
@foreach($option->variables as $variable)
<form action="{{ route('admin.services.option.variable', [$option->service->id, $option->id, $variable->id]) }}" method="POST">
<div class="well">
<div class="row">
<div class="col-md-6 form-group">
<label class="control-label">Variable Name:</label>
<div>
<input type="text" name="{{ $variable->id }}_name" class="form-control" value="{{ old($variable->id.'_name', $variable->name) }}" />
</div>
</div>
<div class="col-md-6 form-group">
<label class="control-label">Variable Description:</label>
<div>
<textarea name="{{ $variable->id }}_description" class="form-control" rows="2">{{ old($variable->id.'_description', $variable->description) }}</textarea>
</div>
</div>
</div>
<div class="row">
<div class="col-md-4 form-group">
<label class="control-label">Environment Variable:</label>
<div>
<input type="text" name="{{ $variable->id }}_env_variable" id="env_var" class="form-control" value="{{ old($variable->id.'_env_variable', $variable->env_variable) }}" />
<p class="text-muted"><small>Accessed in startup by using <code>&#123;&#123;{{ $variable->env_variable }}&#125;&#125;</code> prameter.</small></p>
</div>
</div>
<div class="col-md-4 form-group">
<label class="control-label">Default Value:</label>
<div>
<input type="text" name="{{ $variable->id }}_default_value" class="form-control" value="{{ old($variable->id.'_default_value', $variable->default_value) }}" />
<p class="text-muted"><small>The default value to use for this field.</small></p>
</div>
</div>
<div class="col-md-4 form-group">
<label class="control-label">Regex:</label>
<div>
<input type="text" name="{{ $variable->id }}_regex" class="form-control" value="{{ old($variable->id.'_regex', $variable->regex) }}" />
<p class="text-muted"><small>Regex code to use when verifying the contents of the field.</small></p>
</div>
</div>
</div>
<div class="row fuelux">
<div class="col-md-4">
<div class="checkbox highlight">
<label class="checkbox-custom highlight" data-initialize="checkbox">
<input class="sr-only" name="{{ $variable->id }}_user_viewable" type="checkbox" value="1" @if((int) old($variable->id.'_user_viewable', $variable->user_viewable) === 1)checked="checked"@endif> <strong>User Viewable</strong>
<p class="text-muted"><small>Can users view this variable?</small><p>
</label>
</div>
</div>
<div class="col-md-4">
<div class="checkbox highlight">
<label class="checkbox-custom highlight" data-initialize="checkbox">
<input class="sr-only" name="{{ $variable->id }}_user_editable" type="checkbox" value="1" @if((int) old($variable->id.'_user_editable', $variable->user_editable) === 1)checked="checked"@endif> <strong>User Editable</strong>
<p class="text-muted"><small>Can users edit this variable?</small><p>
</label>
</div>
</div>
<div class="col-md-4">
<div class="checkbox highlight">
<label class="checkbox-custom highlight" data-initialize="checkbox">
<input class="sr-only" name="{{ $variable->id }}_required" type="checkbox" value="1" @if((int) old($variable->id.'_required', $variable->required) === 1)checked="checked"@endif> <strong>Required</strong>
<p class="text-muted"><small>This this variable required?</small><p>
</label>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
{!! csrf_field() !!}
<a href="{{ route('admin.services.option.variable.delete', [$option->service->id, $option->id, $variable->id]) }}"><button type="button" class="btn btn-sm btn-danger pull-right"><i class="fa fa-times"></i></button></a>
<input type="submit" class="btn btn-sm btn-success" value="Update Variable" />
</div>
</div>
</div>
</form>
@endforeach
<h3>Servers</h3><hr />
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>Name</th>
<th>Owner</th>
<th>Updated</th>
</tr>
</thead>
<tbody>
@foreach ($option->servers as $server)
<tr>
<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->email }}</a></td>
<td>{{ $server->updated_at }}</td>
</tr>
@endforeach
</tbody>
</table>
<div class="text-center">
{!! $option->servers->render() !!}
</div>
<form action="{{ route('admin.services.option', [$option->service->id, $option->id]) }}" method="POST">
<div class="row">
<div class="col-md-12">
<div class="alert alert-danger">
Deleting an option is an irreversible action. An option can <em>only</em> be deleted if no servers are associated with it.
</div>
{!! csrf_field() !!}
{!! method_field('DELETE') !!}
<input type="submit" class="btn btn-sm btn-danger pull-right" value="Delete Option" />
</div>
</div>
</form>
</div>
<script>
$(document).ready(function () {
$('#sidebar_links').find("a[href='/admin/services']").addClass('active');
$('#env_var').on('keyup', function () {
$(this).parent().find('code').html('&#123;&#123;' + escape($(this).val()) + '&#125;&#125;');
});
});
</script>
@endsection

View File

@ -1,90 +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')
Service Packs for {{ $option->name }}
@endsection
@section('content')
<div class="col-md-12">
<ul class="breadcrumb">
<li><a href="/admin">Admin Control</a></li>
<li><a href="/admin/services">Services</a></li>
<li><a href="{{ route('admin.services.packs') }}">Packs</a></li>
<li><a href="{{ route('admin.services.packs.service', $option->service->id) }}">{{ $option->service->name }}</a></li>
<li class="active">{{ $option->name }}</li>
</ul>
<h3 class="nopad">Service Packs</h3><hr />
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>Pack Name</th>
<th>Version</th>
<th>UUID</th>
<th>Selectable</th>
<th>Visible</th>
</tr>
</thead>
<tbody>
@foreach ($option->packs as $pack)
<tr>
<td><a href="{{ route('admin.services.packs.edit', $pack->id) }}">{{ $pack->name }}</a></td>
<td><code>{{ $pack->version }}</code></td>
<td><code>{{ $pack->uuid }}</code></td>
<td>@if($pack->selectable)<span class="label label-success"><i class="fa fa-check"></i></span>@else<span class="label label-default"><i class="fa fa-times"></i></span>@endif</td>
<td>@if($pack->visible)<span class="label label-success"><i class="fa fa-check"></i></span>@else<span class="label label-default"><i class="fa fa-times"></i></span>@endif</td>
</tr>
@endforeach
<tr>
<td colspan="5">
<a href="{{ route('admin.services.packs.new', $option->id) }}">
<button class="pull-right btn btn-xxs btn-primary"><i class="fa fa-plus"></i></button>
</a>
<a href="#upload" id="toggleUpload">
<button class="pull-right btn btn-xxs btn-default" style="margin-right:5px;"><i class="fa fa-upload"></i> Install from Template</button>
</a>
</td>
</tr>
</tbody>
</table>
</div>
<script>
$(document).ready(function () {
$('#sidebar_links').find("a[href='/admin/services/packs']").addClass('active');
$('#toggleUpload').on('click', function (event) {
event.preventDefault();
var element = $(this);
element.find('button').addClass('disabled');
$.ajax({
method: 'GET',
url: '{{ route('admin.services.packs.uploadForm', $option->id) }}'
}).fail(function (jqXhr) {
console.error(jqXhr);
alert('There was an error trying to create the upload form.');
}).success(function (data) {
$(data).modal();
}).always(function () {
element.find('button').removeClass('disabled');
});
});
});
</script>
@endsection

View File

@ -1,67 +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')
Service Packs for {{ $service->name }}
@endsection
@section('content')
<div class="col-md-12">
<ul class="breadcrumb">
<li><a href="/admin">Admin Control</a></li>
<li><a href="/admin/services">Services</a></li>
<li><a href="{{ route('admin.services.packs') }}">Packs</a></li>
<li class="active">{{ $service->name }}</li>
</ul>
<h3 class="nopad">Service Packs</h3><hr />
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>Service Option</th>
<th>Total Packs</th>
</tr>
</thead>
<tbody>
@foreach ($service->options as $option)
<tr>
<td><a href="{{ route('admin.services.packs.option', $option->id) }}">{{ $option->name }}</a></td>
<td>{{ $option->packs->count() }}</td>
</tr>
@endforeach
<tr>
<td colspan="2">
<a href="{{ route('admin.services.packs.new') }}">
<button class="pull-right btn btn-xxs btn-primary"><i class="fa fa-plus"></i></button>
</a>
<a href="{{ route('admin.services.packs.new') }}">
<button class="pull-right btn btn-xxs btn-default" style="margin-right:5px;"><i class="fa fa-upload"></i> Install from Template</button>
</a>
</td>
</tr>
</tbody>
</table>
</div>
<script>
$(document).ready(function () {
$('#sidebar_links').find("a[href='/admin/services/packs']").addClass('active');
});
</script>
@endsection

View File

@ -1,160 +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')
Add New Service Pack
@endsection
@section('content')
<div class="col-md-12">
<ul class="breadcrumb">
<li><a href="/admin">Admin Control</a></li>
<li><a href="/admin/services">Services</a></li>
<li><a href="{{ route('admin.services.packs') }}">Packs</a></li>
<li><a href="{{ route('admin.services.packs.service', $pack->option->service->id) }}">{{ $pack->option->service->name }}</a></li>
<li><a href="{{ route('admin.services.packs.option', $pack->option->id) }}">{{ $pack->option->name }}</a></li>
<li class="active">{{ $pack->name }} ({{ $pack->version }})</li>
</ul>
<h3 class="nopad">Manage Service Pack</h3><hr />
<form action="{{ route('admin.services.packs.edit', $pack->id) }}" method="POST">
<div class="row">
<div class="col-md-6 form-group">
<label class="control-label">Pack Name:</label>
<div>
<input type="text" name="name" value="{{ old('name', $pack->name) }}" placeholder="My Awesome Pack" class="form-control" />
<p class="text-muted"><small>The name of the pack which will be seen in dropdown menus and to users.</small></p>
</div>
</div>
<div class="col-md-6 form-group">
<label class="control-label">Pack Version:</label>
<div>
<input type="text" name="version" value="{{ old('version', $pack->version) }}" placeholder="v0.8.1" class="form-control" />
<p class="text-muted"><small>The version of the program included in this pack.</small></p>
</div>
</div>
<div class="col-md-12 form-group">
<label class="control-label">Description:</label>
<div>
<textarea name="description" class="form-control" rows="3">{{ old('description', $pack->description) }}</textarea>
<p class="text-muted"><small>Provide a description of the pack which will be shown to users.</small></p>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label class="control-label">Associated Service Option:</label>
<select name="option" class="form-control">
@foreach($services as $service)
<option disabled>{{ $service->name }}</option>
@foreach($service->options as $option)
<option value="{{ $option->id }}" @if($pack->option_id === $option->id)selected="selected"@endif>&nbsp;&nbsp; -- {{ $option->name }}</option>
@endforeach
@endforeach
</select>
</div>
<div class="col-md-3 fuelux">
<label class="control-label">&nbsp;</label>
<div>
<label class="checkbox-formheight checkbox-custom checkbox-inline highlight" data-initialize="checkbox">
<input class="sr-only" type="checkbox" name="selectable" value="1" @if($pack->selectable)checked="checked"@endif> User Selectable
</label>
</div>
</div>
<div class="col-md-3 fuelux">
<label class="control-label">&nbsp;</label>
<div>
<label class="checkbox-formheight checkbox-custom checkbox-inline highlight" data-initialize="checkbox">
<input class="sr-only" type="checkbox" name="visible" value="1" @if($pack->visible)checked="checked"@endif> Visible
</label>
</div>
</div>
</div>
<hr />
<div class="row">
<div class="col-md-12">
<h5 class="nopad">Package Archive</h5>
<div class="well" style="margin-bottom:0">
<div class="row">
<div class="form-group col-md-12">
@if(count($files) > 1)
<div class="alert alert-danger"><strong>Warning!</strong> Service packs should only contain a single pack archive in <code>.tar.gz</code> format. We've detected more than one file for this pack.</div>
@endif
<table class="table table-striped">
<thead>
<tr>
<th>Filename</th>
<th>File Size</th>
<th>SHA1 Hash</th>
<th>Last Modified</th>
</tr>
</thead>
<tbody>
@foreach($files as &$file)
<tr>
<td>{{ basename($file) }}</td>
<td><code>{{ Storage::size($file) }}</code> Bytes</td>
<td><code>{{ sha1_file(storage_path('app/' . $file)) }}</code></td>
<td>{{ Carbon::createFromTimestamp(Storage::lastModified($file))->toDateTimeString() }}</td>
</tr>
@endforeach
</tbody>
</table>
<p class="text-muted"><small>If you wish to modify or upload a new file it should be uploaded to <code>{{ storage_path('app/packs/' . $pack->uuid) }}</code> as <code>archive.tar.gz</code>.</small></p>
</div>
</div>
</div>
</div>
</div>
<hr />
<div class="row">
<div class="col-md-12">
<div class="form-group">
{!! csrf_field() !!}
<input type="submit" name="action_submit" class="btn btn-sm btn-primary" value="Edit Service Pack" />
<button type="submit" name="action_delete" class="pull-right btn btn-sm btn-danger"><i class="fa fa-times"></i> Delete</button>
<a href="{{ route('admin.services.packs.export', $pack->id) }}"><button type="button" class="pull-right btn btn-sm btn-default" style="margin-right:10px;"><i class="fa fa-file"></i> Export</button></a>
<a href="{{ route('admin.services.packs.export', [ $pack->id, 'true' ]) }}"><button type="button" class="pull-right btn btn-sm btn-default" style="margin-right:10px;"><i class="fa fa-download"></i> Export with Files</button></a>
</div>
</div>
</form>
</div>
{!! Theme::js('js/vendor/ace/ace.js') !!}
{!! Theme::js('js/vendor/ace/ext-modelist.js') !!}
<script type="text/javascript">
$(document).ready(function () {
$('#sidebar_links').find("a[href='/admin/services/packs']").addClass('active');
const Editor = ace.edit('build_script');
Editor.setTheme('ace/theme/chrome');
Editor.getSession().setUseWrapMode(true);
Editor.setShowPrintMargin(false);
Editor.getSession().setMode('ace/mode/sh');
Editor.setOptions({
minLines: 12,
maxLines: Infinity
});
Editor.on('change', event => {
$('#editor_contents').val(Editor.getValue());
});
});
</script>
@endsection

View File

@ -1,47 +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')
Service Packs
@endsection
@section('content')
<div class="col-md-12">
<ul class="breadcrumb">
<li><a href="/admin">Admin Control</a></li>
<li><a href="/admin/services">Services</a></li>
<li class="active">Packs</li>
</ul>
<h3 class="nopad">Service Packs</h3><hr />
<div class="row">
@foreach ($services as $service)
<div class="col-md-6">
<a href="{{ route('admin.services.packs.service', $service->id) }}"><button class="btn btn-lg btn-primary" style="width:100%;margin-bottom:25px;">{{ $service->name }}</button></a>
</div>
@endforeach
</div>
</div>
<script>
$(document).ready(function () {
$('#sidebar_links').find("a[href='/admin/services/packs']").addClass('active');
});
</script>
@endsection

View File

@ -1,135 +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')
Add New Service Pack
@endsection
@section('content')
<div class="col-md-12">
<ul class="breadcrumb">
<li><a href="/admin">Admin Control</a></li>
<li><a href="/admin/services">Services</a></li>
<li class="active">New Service Pack</li>
</ul>
<h3 class="nopad">New Service Pack</h3><hr />
<form action="{{ route('admin.services.packs.new') }}" method="POST" enctype="multipart/form-data">
<div class="row">
<div class="col-md-6 form-group">
<label class="control-label">Pack Name:</label>
<div>
<input type="text" name="name" value="{{ old('name') }}" placeholder="My Awesome Pack" class="form-control" />
<p class="text-muted"><small>The name of the pack which will be seen in dropdown menus and to users.</small></p>
</div>
</div>
<div class="col-md-6 form-group">
<label class="control-label">Pack Version:</label>
<div>
<input type="text" name="version" value="{{ old('version') }}" placeholder="v0.8.1" class="form-control" />
<p class="text-muted"><small>The version of the program included in this pack.</small></p>
</div>
</div>
<div class="col-md-12 form-group">
<label class="control-label">Description:</label>
<div>
<textarea name="description" class="form-control" rows="3">{{ old('description') }}</textarea>
<p class="text-muted"><small>Provide a description of the pack which will be shown to users.</small></p>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label class="control-label">Associated Service Option:</label>
<select name="option" class="form-control">
@foreach($services as $service)
<option disabled>{{ $service->name }}</option>
@foreach($service->options as $option)
<option value="{{ $option->id }}" @if((int) request()->option === $option->id)selected="selected"@endif>&nbsp;&nbsp; -- {{ $option->name }}</option>
@endforeach
@endforeach
</select>
</div>
<div class="col-md-3 fuelux">
<label class="control-label">&nbsp;</label>
<div>
<label class="checkbox-formheight checkbox-custom checkbox-inline highlight" data-initialize="checkbox">
<input class="sr-only" type="checkbox" name="selectable" value="1"> User Selectable
</label>
</div>
</div>
<div class="col-md-3 fuelux">
<label class="control-label">&nbsp;</label>
<div>
<label class="checkbox-formheight checkbox-custom checkbox-inline highlight" data-initialize="checkbox">
<input class="sr-only" type="checkbox" name="visible" value="1"> Visible
</label>
</div>
</div>
</div>
<hr />
<div class="row">
<div class="col-md-12">
<h5 class="nopad">File Upload</h5>
<div class="well" style="margin-bottom:0">
<div class="row">
<div class="form-group col-md-12">
<label class="control-label">Package Archive:</label>
<input name="file_upload" type="file" accept=".tar.gz, application/gzip" />
<p class="text-muted"><small>This package file must be a <code>.tar.gz</code> archive of files to use for either building or running this pack.<br /><br />If your file is larger than <code>20MB</code> we recommend uploading it using SFTP. Once you have added this pack to the system, a path will be provided where you should upload the file.
This server is currently configured with the following limits: <code>upload_max_filesize={{ ini_get('upload_max_filesize') }}</code> and <code>post_max_size={{ ini_get('post_max_size') }}</code>. If your file is larger than either of those values this request will fail.</small></p>
</div>
</div>
</div>
</div>
</div>
<hr />
<div class="row">
<div class="col-md-12">
<div class="form-group">
{!! csrf_field() !!}
<input type="submit" class="btn btn-sm btn-primary" value="Add Service Pack" />
</div>
</div>
</form>
</div>
{!! Theme::js('js/vendor/ace/ace.js') !!}
{!! Theme::js('js/vendor/ace/ext-modelist.js') !!}
<script type="text/javascript">
$(document).ready(function () {
$('#sidebar_links').find("a[href='/admin/services/packs']").addClass('active');
const Editor = ace.edit('build_script');
Editor.setTheme('ace/theme/chrome');
Editor.getSession().setUseWrapMode(true);
Editor.setShowPrintMargin(false);
Editor.getSession().setMode('ace/mode/sh');
Editor.setOptions({
minLines: 12,
maxLines: Infinity
});
Editor.setValue('{{ old('build_script') }}');
Editor.on('change', event => {
$('#editor_contents').val(Editor.getValue());
});
});
</script>
@endsection

View File

@ -1,135 +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')
Manage Service
@endsection
@section('content')
<div class="col-md-12">
<ul class="breadcrumb">
<li><a href="/admin">Admin Control</a></li>
<li><a href="/admin/services">Services</a></li>
<li class="active">{{ $service->name }}</li>
</ul>
<h3 class="nopad">Service Options</h3><hr />
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>Option Name</th>
<th>Description</th>
<th>Tag</th>
<th class="text-center">Servers</th>
</tr>
</thead>
<tbody>
@foreach($service->options as $option)
<tr>
<td><a href="{{ route('admin.services.option', [ $service->id, $option->id]) }}">{{ $option->name }}</a></td>
<td>{!! $option->description !!}</td>
<td><code>{{ $option->tag }}</code></td>
<td class="text-center">{{ $option->servers->count() }}</td>
</tr>
@endforeach
<tr>
<td></td>
<td></td>
<td></td>
<td class="text-center"><a href="{{ route('admin.services.option.new', $service->id) }}"><i class="fa fa-plus"></i></a></td>
</tr>
</tbody>
</table>
<div class="well">
<form action="{{ route('admin.services.service', $service->id) }}" method="POST">
<div class="row">
<div class="col-md-6 form-group">
<label class="control-label">Service Name:</label>
<div>
<input type="text" name="name" class="form-control" value="{{ old('name', $service->name) }}" />
<p class="text-muted"><small>This should be a descriptive category name that emcompasses all of the options within the service.</small></p>
</div>
</div>
<div class="col-md-6 form-group">
<label class="control-label">Service Description:</label>
<div>
<textarea name="description" class="form-control" rows="4">{{ old('description', $service->description) }}</textarea>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 form-group">
<label class="control-label">Service Configuration File:</label>
<div class="input-group">
<span class="input-group-addon">/src/services/</span>
<input type="text" name="file" class="form-control" value="{{ old('file', $service->file) }}" />
<span class="input-group-addon">/index.js</span>
</div>
<p class="text-muted"><small>This should be the name of the folder on the daemon that contains all of the service logic. Changing this can have unintended effects on servers or causes errors to occur.</small></p>
</div>
<div class="col-md-6 form-group">
<label class="control-label">Display Executable:</label>
<div>
<input type="text" name="executable" class="form-control" value="{{ old('executable', $service->executable) }}" />
</div>
<p class="text-muted"><small>Changing this has no effect on operation of the daemon, it is simply used for display purposes on the panel. This can be changed per-option.</small></p>
</div>
</div>
<div class="row">
<div class="col-md-12 form-group">
<label class="control-label">Default Startup:</label>
<div class="input-group">
<span class="input-group-addon" id="disp_exec">{{ $service->executable }}</span>
<input type="text" name="startup" class="form-control" value="{{ old('startup', $service->startup) }}" />
</div>
<p class="text-muted"><small>This is the default startup that will be used for all servers created using this service. This can be changed per-option.</small></p>
</div>
</div>
<div class="row">
<div class="col-md-12">
{!! csrf_field() !!}
<input type="submit" class="btn btn-sm btn-primary" value="Save Changes" />
<a href="{{ route('admin.services.service.config', $service->id) }}"><button type="button" class="pull-right btn btn-sm btn-default">Manage Configuration</button></a>
</div>
</div>
</form>
</div>
<form action="{{ route('admin.services.service', $service->id) }}" method="POST">
<div class="row">
<div class="col-md-12">
<div class="alert alert-danger">
Deleting a service is an irreversible action. A service can <em>only</em> be deleted if no servers are associated with it.
</div>
{!! csrf_field() !!}
{!! method_field('DELETE') !!}
<input type="submit" class="btn btn-sm btn-danger pull-right" value="Delete Service" />
</div>
</div>
</form>
</div>
<script>
$(document).ready(function () {
$('#sidebar_links').find("a[href='/admin/services']").addClass('active');
$('input[name="executable"]').on('keyup', function() {
$("#disp_exec").html(escape($(this).val()));
});
});
</script>
@endsection

View File

@ -1,37 +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.master')
@section('title', '403: Forbidden')
@section('content')
<div class="col-md-12">
<div class="panel panel-danger">
<div class="panel-heading">
<h3 class="panel-title">HTTP 403: Access Denied</h3>
</div>
<div class="panel-body">
<p style="margin-bottom:0;">You do not have permission to access that function. Please contact your server administrator to request permission.</p>
</div>
</div>
<p style="text-align:center;"><img src="{{ Theme::url('images/403.jpg') }}" /></p>
<p style="text-align:center;"><a href="{{ URL::previous() }}">Take me back</a> or <a href="/">go home</a>.</p>
</div>
@endsection

View File

@ -1,37 +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.master')
@section('title', '404: Not Found')
@section('right-nav')
@endsection
@section('sidebar')
@endsection
@section('content')
<div class="col-md-8">
<h1 class="text-center">404 - File Not Found</h1>
<p class="text-center"><img src="{{ Theme::url('images/404.jpg') }}" /></p>
<p class="text-center"><a href="{{ URL::previous() }}">Take me back</a> or <a href="/">go home</a>.</p>
</div>
@endsection

View File

@ -1,226 +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. --}}
<!DOCTYPE html>
<html lang="en">
<head>
@section('scripts')
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="noindex">
{!! Theme::css('css/vendor/bootstrap/bootstrap.css') !!}
{!! Theme::css('css/pterodactyl.css') !!}
{!! Theme::css('css/animate.css') !!}
{!! Theme::css('css/vendor/fontawesome/font-awesome.min.css') !!}
{!! Theme::css('css/vendor/sweetalert/sweetalert.min.css') !!}
{!! Theme::css('css/vendor/fuelux/fuelux.min.css') !!}
{!! Theme::js('js/vendor/jquery/jquery.min.js') !!}
{!! Theme::js('js/vendor/bootstrap/bootstrap.min.js') !!}
{!! Theme::js('js/vendor/sweetalert/sweetalert.min.js') !!}
{!! Theme::js('js/vendor/fuelux/fuelux.min.js') !!}
{!! Theme::js('js/admin.min.js') !!}
{!! Theme::js('js/bootstrap-notify.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>
@show
<title>{{ Settings::get('company') }} - @yield('title')</title>
</head>
<body>
<div class="container">
<div class="navbar navbar-default">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-responsive-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">{{ Settings::get('company', 'Pterodactyl Panel') }}</a>
</div>
<div class="navbar-collapse collapse navbar-responsive-collapse">
@section('navbar-links')
<ul class="nav navbar-nav hidden-md hidden-lg">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Management <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="/admin">Admin Index</a></li>
<li><a href="/admin/settings">General Settings</a></li>
<li><a href="/admin/databases">Database Management</a></li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Users <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="/admin/users">Find Account</a></li>
<li><a href="/admin/users/new">New Account</a></li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Servers <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="/admin/servers">Find Server</a></li>
<li><a href="/admin/servers/new">New Server</a></li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Nodes <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="/admin/nodes">List Nodes</a></li>
<li><a href="/admin/locations">Manage Locations</a></li>
<li><a href="/admin/nodes/new">New Node</a></li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Services <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="/admin/services/packs">Service Packs</a></li>
<li><a href="/admin/services">List Services</a></li>
<li><a href="/admin/services/new">Add Service</a></li>
</ul>
</li>
</ul>
@show
@section('right-nav')
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">{{ trans('strings.language') }}<b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="/language/de">Deutsch</a></li>
<li><a href="/language/en">English</a></li>
<!-- <li><a href="/language/es">Espa&ntilde;ol</a></li>
<li><a href="/language/fr">Fran&ccedil;ais</a></li>
<li><a href="/language/it">Italiano</a></li>
<li><a href="/language/pl">Polski</a></li> -->
<li><a href="/language/pt">Portugu&ecirc;s</a></li>
<!-- <li><a href="/language/ru">&#1088;&#1091;&#1089;&#1089;&#1082;&#1080;&#1081;</a></li>
<li><a href="/language/se">Svenska</a></li>
<li><a href="/language/zh">&#20013;&#22269;&#30340;的</a></li> -->
</ul>
</li>
<li class="hidden-xs"><a href="/"><i class="fa fa-server"></i></a></li>
<li class="visible-xs"><a href="/"><i class="fa fa-server"></i> Server View</a></li>
<li class="hidden-xs"><a href="/auth/logout"><i class="fa fa-power-off"></i></a></li>
<li class="visible-xs"><a href="/auth/logout"><i class="fa fa-power-off"></i> Logout</a></li>
</ul>
@show
</div>
</div>
<!-- Add Back Mobile Support -->
<div class="row">
<div class="col-md-3 hidden-xs hidden-sm" id="sidebar_links">
@section('sidebar')
<div class="list-group">
<a href="#" class="list-group-item list-group-item-heading"><strong>Management</strong></a>
<a href="/admin" id="sidenav_admin-index" class="list-group-item">Admin Index</a>
<a href="/admin/settings" class="list-group-item">General Settings</a>
<a href="/admin/databases" class="list-group-item">Database Management</a>
</div>
<div class="list-group">
<a href="#" class="list-group-item list-group-item-heading"><strong>Account Management</strong></a>
<a href="/admin/users" class="list-group-item">Find Account</a>
<a href="/admin/users/new" class="list-group-item">New Account</a>
</div>
<div class="list-group">
<a href="#" class="list-group-item list-group-item-heading"><strong>Server Management</strong></a>
<a href="/admin/servers" class="list-group-item">Find Server</a>
<a href="/admin/servers/new" class="list-group-item">New Server</a>
</div>
<div class="list-group">
<a href="#" class="list-group-item list-group-item-heading"><strong>Node Management</strong></a>
<a href="/admin/nodes" class="list-group-item">List Nodes</a>
<a href="/admin/locations" class="list-group-item">Manage Locations</a>
<a href="/admin/nodes/new" class="list-group-item">Add Node</a>
</div>
<div class="list-group">
<a href="#" class="list-group-item list-group-item-heading"><strong>Service Management</strong></a>
<a href="/admin/services/packs" class="list-group-item">Service Packs</a>
<a href="/admin/services" class="list-group-item">List Services</a>
<a href="/admin/services/new" class="list-group-item">Add Service</a>
</div>
@show
</div>
<div class="col-md-9">
<div class="row">
<div class="col-md-12" id="tpl_messages">
@section('resp-errors')
@if (count($errors) > 0)
<div class="alert alert-danger">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<strong>{{ trans('strings.whoops') }}!</strong> {{ trans('auth.errorencountered') }}<br><br>
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
@show
@section('resp-alerts')
@foreach (Alert::getMessages() as $type => $messages)
@foreach ($messages as $message)
<div class="alert alert-{{ $type }} alert-dismissable" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
{!! $message !!}
</div>
@endforeach
@endforeach
@show
</div>
</div>
<div class="row">
@yield('content')
</div>
</div>
</div>
<div class="footer">
<div class="row" style="margin-bottom:15px;">
<div class="col-md-12">
Copyright &copy; 2015 - {{ date('Y') }} <a href="https://github.com/Pterodactyl/Panel" target="_blank">Pterodactyl Software &amp; Design</a>.<br />
Pterodactyl is licensed under a <a href="https://opensource.org/licenses/MIT" target="_blank">MIT</a> license. <!-- Please do not remove this license notice. We can't stop you though... :) -->
</div>
</div>
</div>
</div>
<script>
$(document).ready(function () {
@if (count($errors) > 0)
@foreach ($errors->all() as $error)
<?php preg_match('/^The\s(.*?)\sfield/', $error, $matches) ?>
@if (isset($matches[1]))
$('[name="{{ str_replace(' ', '_', $matches[1]) }}"]').parent().parent().addClass('has-error');
@endif
@endforeach
@endif
});
</script>
</body>
</html>

View File

@ -1,31 +0,0 @@
'use strict';
/**
* Pterodactyl - Daemon
* 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.
*/
const rfr = require('rfr');
const Core = rfr('src/services/index.js');
class Service extends Core {}
module.exports = Service;

View File

@ -1,74 +0,0 @@
{
"vanilla": {
"startup": {
"done": ")! For help, type ",
"userInteraction": [
"Go to eula.txt for more info."
]
},
"stop": "stop",
"configs": {
"server.properties": {
"parser": "properties",
"find": {
"server-ip": "0.0.0.0",
"enable-query": "true",
"server-port": "{{ server.build.default.port }}",
"query.port": "{{ server.build.default.port }}"
}
}
},
"log": {
"custom": false,
"location": "logs/latest.log"
},
"query": "minecraftping"
},
"spigot": {
"symlink": "vanilla",
"configs": {
"spigot.yml": {
"parser": "yaml",
"find": {
"settings.restart-on-crash": "false"
}
}
}
},
"bungeecord": {
"startup": {
"done": "Listening on ",
"userInteraction": [
"Listening on /0.0.0.0:25577"
]
},
"stop": "end",
"configs": {
"config.yml": {
"parser": "yaml",
"find": {
"listeners[0].query_enabled": true,
"listeners[0].query_port": "{{ server.build.default.port }}",
"listeners[0].host": "0.0.0.0:{{ server.build.default.port }}",
"servers.*.address": {
"127.0.0.1": "{{ config.docker.interface }}",
"localhost": "{{ config.docker.interface }}"
}
}
}
},
"log": {
"custom": false,
"location": "proxy.log.0"
},
"query": "minecraftping"
},
"sponge": {
"symlink": "vanilla",
"startup": {
"userInteraction": [
"You need to agree to the EULA"
]
}
}
}

View File

@ -1,31 +0,0 @@
'use strict';
/**
* Pterodactyl - Daemon
* 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.
*/
const rfr = require('rfr');
const Core = rfr('src/services/index.js');
class Service extends Core {}
module.exports = Service;

View File

@ -1,23 +0,0 @@
{
"srcds": {
"startup": {
"done": "Assigned anonymous gameserver Steam ID",
"userInteraction": []
},
"stop": "quit",
"configs": {},
"log": {
"custom": true,
"location": "logs/latest.log"
},
"query": "protocol-valve"
},
"ark": {
"symlink": "srcds",
"startup": {
"done": "Setting breakpad minidump AppID"
},
"stop": "^C",
"query": "none"
}
}

View File

@ -1,31 +0,0 @@
'use strict';
/**
* Pterodactyl - Daemon
* 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.
*/
const rfr = require('rfr');
const Core = rfr('src/services/index.js');
class Service extends Core {}
module.exports = Service;

View File

@ -1,23 +0,0 @@
{
"tshock": {
"startup": {
"done": "Type 'help' for a list of commands",
"userInteraction": []
},
"stop": "exit",
"configs": {
"tshock/config.json": {
"parser": "json",
"find": {
"ServerPort": "{{ server.build.default.port }}",
"MaxSlots": "{{ server.build.env.MAX_SLOTS }}"
}
}
},
"log": {
"custom": false,
"location": "ServerLog.txt"
},
"query": "none"
}
}

View File

@ -1,31 +0,0 @@
'use strict';
/**
* Pterodactyl - Daemon
* 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.
*/
const rfr = require('rfr');
const Core = rfr('src/services/index.js');
class Service extends Core {}
module.exports = Service;

View File

@ -1,50 +0,0 @@
{
"mumble": {
"startup": {
"done": "Server listening on",
"userInteraction": [
"Generating new server certificate"
]
},
"stop": "^C",
"configs": {
"murmur.ini": {
"parser": "ini",
"find": {
"logfile": "murmur.log",
"port": "{{ server.build.default.port }}",
"host": "0.0.0.0",
"users": "{{ server.build.env.MAX_USERS }}"
}
}
},
"log": {
"custom": true,
"location": "logs/murmur.log"
},
"query": "mumbleping"
},
"ts3": {
"startup": {
"done": "listening on 0.0.0.0:",
"userInteraction": []
},
"stop": "^C",
"configs": {
"ts3server.ini": {
"parser": "ini",
"find": {
"default_voice_port": "{{ server.build.default.port }}",
"voice_ip": "0.0.0.0",
"query_port": "{{ server.build.default.port }}",
"query_ip": "0.0.0.0"
}
}
},
"log": {
"custom": true,
"location": "logs/ts3.log"
},
"query": "none"
}
}