Skip to content

Commit d7682bb

Browse files
committed
Complete new service, option, and variable management interface in Admin CP
1 parent bccbb30 commit d7682bb

File tree

16 files changed

+699
-926
lines changed

16 files changed

+699
-926
lines changed

app/Http/Controllers/Admin/OptionController.php

Lines changed: 118 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,63 @@
2727
use Log;
2828
use Alert;
2929
use Storage;
30-
use Pterodactyl\Models;
30+
use Javascript;
3131
use Illuminate\Http\Request;
32+
use Pterodactyl\Models\Service;
33+
use Pterodactyl\Models\ServiceOption;
3234
use Pterodactyl\Exceptions\DisplayException;
3335
use Pterodactyl\Http\Controllers\Controller;
3436
use Pterodactyl\Repositories\OptionRepository;
37+
use Pterodactyl\Repositories\VariableRepository;
3538
use Pterodactyl\Exceptions\DisplayValidationException;
3639

3740
class OptionController extends Controller
3841
{
42+
/**
43+
* Handles request to view page for adding new option.
44+
*
45+
* @param Request $request
46+
* @return \Illuminate\View\View
47+
*/
48+
public function new(Request $request)
49+
{
50+
$services = Service::with('options')->get();
51+
Javascript::put(['services' => $services->keyBy('id')]);
52+
53+
return view('admin.services.options.new', ['services' => $services]);
54+
}
55+
56+
/**
57+
* Handles POST request to create a new option.
58+
59+
* @param Request $request
60+
* @return \Illuminate\Response\RedirectResponse
61+
*/
62+
public function create(Request $request)
63+
{
64+
$repo = new OptionRepository;
65+
66+
try {
67+
$option = $repo->create($request->intersect([
68+
'service_id', 'name', 'description', 'tag',
69+
'docker_image', 'startup', 'config_from', 'config_startup',
70+
'config_logs', 'config_files', 'config_stop'
71+
]));
72+
Alert::success('Successfully created new service option.')->flash();
73+
74+
return redirect()->route('admin.services.option.view', $option->id);
75+
} catch (DisplayValidationException $ex) {
76+
return redirect()->route('admin.services.option.new')->withErrors(json_decode($ex->getMessage()))->withInput();
77+
} catch (DisplayException $ex) {
78+
Alert::danger($ex->getMessage())->flash();
79+
} catch (\Exception $ex) {
80+
Log::error($ex);
81+
Alert::danger('An unhandled exception occurred while attempting to create this service. This error has been logged.')->flash();
82+
}
83+
84+
return redirect()->route('admin.services.option.new')->withInput();
85+
}
86+
3987
/**
4088
* Display option overview page.
4189
*
@@ -45,27 +93,89 @@ class OptionController extends Controller
4593
*/
4694
public function viewConfiguration(Request $request, $id)
4795
{
48-
return view('admin.services.options.view', ['option' => Models\ServiceOption::findOrFail($id)]);
96+
return view('admin.services.options.view', ['option' => ServiceOption::findOrFail($id)]);
97+
}
98+
99+
/**
100+
* Display variable overview page for a service option.
101+
*
102+
* @param Request $request
103+
* @param int $id
104+
* @return \Illuminate\View\View
105+
*/
106+
public function viewVariables(Request $request, $id)
107+
{
108+
return view('admin.services.options.variables', ['option' => ServiceOption::with('variables')->findOrFail($id)]);
49109
}
50110

111+
/**
112+
* Handles POST when editing a configration for a service option.
113+
*
114+
* @param Request $request
115+
* @return \Illuminate\Response\RedirectResponse
116+
*/
51117
public function editConfiguration(Request $request, $id)
52118
{
53119
$repo = new OptionRepository;
54120

55121
try {
56-
$repo->update($id, $request->intersect([
57-
'name', 'description', 'tag', 'docker_image', 'startup',
58-
'config_from', 'config_stop', 'config_logs', 'config_files', 'config_startup',
59-
]));
122+
if ($request->input('action') !== 'delete') {
123+
$repo->update($id, $request->intersect([
124+
'name', 'description', 'tag', 'docker_image', 'startup',
125+
'config_from', 'config_stop', 'config_logs', 'config_files', 'config_startup',
126+
]));
127+
Alert::success('Service option configuration has been successfully updated.')->flash();
128+
} else {
129+
$option = ServiceOption::with('service')->where('id', $id)->first();
130+
$repo->delete($id);
131+
Alert::success('Successfully deleted service option from the system.')->flash();
60132

61-
Alert::success('Service option configuration has been successfully updated.')->flash();
133+
return redirect()->route('admin.services.view', $option->service_id);
134+
}
62135
} catch (DisplayValidationException $ex) {
63136
return redirect()->route('admin.services.option.view', $id)->withErrors(json_decode($ex->getMessage()));
137+
} catch (DisplayException $ex) {
138+
Alert::danger($ex->getMessage())->flash();
64139
} catch (\Exception $ex) {
65140
Log::error($ex);
66-
Alert::danger('An unhandled exception occurred while attempting to update this service option. This error has been logged.')->flash();
141+
Alert::danger('An unhandled exception occurred while attempting to perform that action. This error has been logged.')->flash();
67142
}
68143

69144
return redirect()->route('admin.services.option.view', $id);
70145
}
146+
147+
/**
148+
* Handles POST when editing a configration for a service option.
149+
*
150+
* @param Request $request
151+
* @param int $option
152+
* @param int $variable
153+
* @return \Illuminate\Response\RedirectResponse
154+
*/
155+
public function editVariable(Request $request, $option, $variable)
156+
{
157+
$repo = new VariableRepository;
158+
159+
try {
160+
if ($request->input('action') !== 'delete') {
161+
$variable = $repo->update($variable, $request->only([
162+
'name', 'description', 'env_variable',
163+
'default_value', 'options', 'rules',
164+
]));
165+
Alert::success("The service variable '{$variable->name}' has been updated.")->flash();
166+
} else {
167+
$repo->delete($variable);
168+
Alert::success("That service variable has been deleted.")->flash();
169+
}
170+
} catch (DisplayValidationException $ex) {
171+
return redirect()->route('admin.services.option.variables', $option)->withErrors(json_decode($ex->getMessage()));
172+
} catch (DisplayException $ex) {
173+
Alert::danger($ex->getMessage())->flash();
174+
} catch (\Exception $ex) {
175+
Log::error($ex);
176+
Alert::danger('An unhandled exception was encountered while attempting to process that request. This error has been logged.')->flash();
177+
}
178+
179+
return redirect()->route('admin.services.option.variables', $option);
180+
}
71181
}

app/Http/Controllers/Admin/ServiceController.php

Lines changed: 11 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -103,32 +103,6 @@ public function create(Request $request)
103103
return redirect()->route('admin.services.new')->withInput();
104104
}
105105

106-
/**
107-
* Delete a service from the system.
108-
*
109-
* @param Request $request
110-
* @param int $id
111-
* @return \Illuminate\Response\RedirectResponse
112-
*/
113-
public function delete(Request $request, $id)
114-
{
115-
$repo = new ServiceRepository;
116-
117-
try {
118-
$repo->delete($id);
119-
Alert::success('Successfully deleted service.')->flash();
120-
121-
return redirect()->route('admin.services');
122-
} catch (DisplayException $ex) {
123-
Alert::danger($ex->getMessage())->flash();
124-
} catch (\Exception $ex) {
125-
Log::error($ex);
126-
Alert::danger('An error was encountered while attempting to delete that service. This error has been logged')->flash();
127-
}
128-
129-
return redirect()->route('admin.services.view', $id);
130-
}
131-
132106
/**
133107
* Edits configuration for a specific service.
134108
*
@@ -141,10 +115,17 @@ public function edit(Request $request, $id)
141115
$repo = new ServiceRepository;
142116

143117
try {
144-
$repo->update($id, $request->intersect([
145-
'name', 'description', 'folder', 'startup',
146-
]));
147-
Alert::success('Service has been updated successfully.')->flash();
118+
if ($request->input('action') !== 'delete') {
119+
$repo->update($id, $request->intersect([
120+
'name', 'description', 'folder', 'startup',
121+
]));
122+
Alert::success('Service has been updated successfully.')->flash();
123+
} else {
124+
$repo->delete($id);
125+
Alert::success('Successfully deleted service from the system.')->flash();
126+
127+
return redirect()->route('admin.services');
128+
}
148129
} catch (DisplayValidationException $ex) {
149130
return redirect()->route('admin.services.view', $id)->withErrors(json_decode($ex->getMessage()))->withInput();
150131
} catch (DisplayException $ex) {

app/Http/Routes/AdminRoutes.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -419,20 +419,25 @@ public function map(Router $router)
419419
'uses' => 'Admin\OptionController@new',
420420
]);
421421

422+
$router->post('/option/new', 'Admin\OptionController@create');
423+
422424
$router->get('/option/{id}', [
423425
'as' => 'admin.services.option.view',
424426
'uses' => 'Admin\OptionController@viewConfiguration',
425427
]);
426428

427429
$router->get('/option/{id}/variables', [
428-
'as' => 'admin.services.option.view.variables',
430+
'as' => 'admin.services.option.variables',
429431
'uses' => 'Admin\OptionController@viewVariables',
430432
]);
431433

432-
$router->post('/option/{id}', [
433-
'uses' => 'Admin\OptionController@editConfiguration',
434+
$router->post('/option/{id}/variables/{variable}', [
435+
'as' => 'admin.services.option.variables.edit',
436+
'uses' => 'Admin\OptionController@editVariable',
434437
]);
435438

439+
$router->post('/option/{id}', 'Admin\OptionController@editConfiguration');
440+
436441
});
437442

438443
// Service Packs

app/Models/Service.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class Service extends Model
4141
* @var array
4242
*/
4343
protected $fillable = [
44-
'name', 'description', 'file', 'executable', 'startup',
44+
'name', 'description', 'folder', 'startup',
4545
];
4646

4747
/**

app/Repositories/OptionRepository.php

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,100 @@
2828
use Validator;
2929
use Pterodactyl\Models\ServiceOption;
3030
use Pterodactyl\Exceptions\DisplayException;
31+
use Pterodactyl\Repositories\VariableRepository;
3132
use Pterodactyl\Exceptions\DisplayValidationException;
3233

3334
class OptionRepository
3435
{
36+
/**
37+
* Creates a new service option on the system.
38+
*
39+
* @param array $data
40+
* @return \Pterodactyl\Models\ServiceOption
41+
*
42+
* @throws \Pterodactyl\Exceptions\DisplayException
43+
* @throws \Pterodactyl\Exceptions\DisplayValidationException
44+
*/
45+
public function create(array $data)
46+
{
47+
$validator = Validator::make($data, [
48+
'service_id' => 'required|numeric|exists:services,id',
49+
'name' => 'required|string|max:255',
50+
'description' => 'required|string',
51+
'tag' => 'required|string|max:255|unique:service_options,tag',
52+
'docker_image' => 'required|string|max:255',
53+
'startup' => 'required|string',
54+
'config_from' => 'sometimes|required|numeric|exists:service_options,id',
55+
'config_startup' => 'required_without:config_from|json',
56+
'config_stop' => 'required_without:config_from|string|max:255',
57+
'config_logs' => 'required_without:config_from|json',
58+
'config_files' => 'required_without:config_from|json',
59+
]);
60+
61+
if ($validator->fails()) {
62+
throw new DisplayValidationException($validator->errors());
63+
}
64+
65+
if (isset($data['config_from'])) {
66+
if (! ServiceOption::where('service_id', $data['service_id'])->where('id', $data['config_from'])->first()) {
67+
throw new DisplayException('The `configuration from` directive must be a child of the assigned service.');
68+
}
69+
}
70+
71+
return ServiceOption::create($data);
72+
}
73+
74+
/**
75+
* Deletes a service option from the system.
76+
*
77+
* @param int $id
78+
* @return void
79+
*
80+
* @throws \Pterodactyl\Exceptions\DisplayException
81+
*/
82+
public function delete($id)
83+
{
84+
$option = ServiceOption::with('variables')->withCount('servers')->findOrFail($id);
85+
86+
if ($option->servers_count > 0) {
87+
throw new DisplayException('You cannot delete a service option that has servers associated with it.');
88+
}
89+
90+
DB::transaction(function () use ($option) {
91+
foreach($option->variables as $variable) {
92+
(new VariableRepository)->delete($variable->id);
93+
}
94+
95+
$option->delete();
96+
});
97+
}
98+
3599
/**
36100
* Updates a service option in the database which can then be used
37101
* on nodes.
38102
*
39103
* @param int $id
40104
* @param array $data
41105
* @return \Pterodactyl\Models\ServiceOption
106+
*
107+
* @throws \Pterodactyl\Exceptions\DisplayException
108+
* @throws \Pterodactyl\Exceptions\DisplayValidationException
42109
*/
43110
public function update($id, array $data)
44111
{
45112
$option = ServiceOption::findOrFail($id);
46113

114+
// Due to code limitations (at least when I am writing this currently)
115+
// we have to make an assumption that if config_from is not passed
116+
// that we should be telling it that no config is wanted anymore.
117+
//
118+
// This really is only an issue if we open API access to this function,
119+
// in which case users will always need to pass `config_from` in order
120+
// to keep it assigned.
121+
if (! isset($data['config_from']) && ! is_null($option->config_from)) {
122+
$option->config_from = null;
123+
}
124+
47125
$validator = Validator::make($data, [
48126
'name' => 'sometimes|required|string|max:255',
49127
'description' => 'sometimes|required|string',
@@ -73,6 +151,12 @@ public function update($id, array $data)
73151
throw new DisplayValidationException($validator->errors());
74152
}
75153

154+
if (isset($data['config_from'])) {
155+
if (! ServiceOption::where('service_id', $option->service_id)->where('id', $data['config_from'])->first()) {
156+
throw new DisplayException('The `configuration from` directive must be a child of the assigned service.');
157+
}
158+
}
159+
76160
$option->fill($data)->save();
77161

78162
return $option;

0 commit comments

Comments
 (0)