From 5500dcc4d5519e410015f296570520cb7e9a35e2 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Mon, 15 Feb 2016 12:39:17 -0500 Subject: [PATCH 01/19] bump version --- config/app.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/app.php b/config/app.php index 4456d8e5..c6678133 100644 --- a/config/app.php +++ b/config/app.php @@ -4,7 +4,7 @@ return [ 'env' => env('APP_ENV', 'production'), - 'version' => env('APP_VERSION', 'v0.2.0-beta'), + 'version' => env('APP_VERSION', 'v0.2.1-beta'), /* |-------------------------------------------------------------------------- From ad5e253a070077cd6c410375291d4b1a8c02b8ad Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Mon, 15 Feb 2016 15:21:28 -0500 Subject: [PATCH 02/19] Really basic initial implementation of service management --- .../Controllers/Admin/ServiceController.php | 145 ++++++++++++++++++ app/Http/Routes/AdminRoutes.php | 38 +++++ app/Models/Service.php | 7 + app/Models/ServiceOptions.php | 7 + app/Models/ServiceVariables.php | 7 + app/Repositories/ServiceRepository/Option.php | 48 ++++++ .../ServiceRepository/Service.php | 63 ++++++++ .../ServiceRepository/Variable.php | 77 ++++++++++ .../views/admin/services/index.blade.php | 55 +++++++ resources/views/admin/services/new.blade.php | 0 .../admin/services/options/new.blade.php | 0 .../admin/services/options/view.blade.php | 140 +++++++++++++++++ resources/views/admin/services/view.blade.php | 118 ++++++++++++++ 13 files changed, 705 insertions(+) create mode 100644 app/Http/Controllers/Admin/ServiceController.php create mode 100644 app/Repositories/ServiceRepository/Option.php create mode 100644 app/Repositories/ServiceRepository/Service.php create mode 100644 app/Repositories/ServiceRepository/Variable.php create mode 100644 resources/views/admin/services/index.blade.php create mode 100644 resources/views/admin/services/new.blade.php create mode 100644 resources/views/admin/services/options/new.blade.php create mode 100644 resources/views/admin/services/options/view.blade.php create mode 100644 resources/views/admin/services/view.blade.php diff --git a/app/Http/Controllers/Admin/ServiceController.php b/app/Http/Controllers/Admin/ServiceController.php new file mode 100644 index 00000000..981a8b32 --- /dev/null +++ b/app/Http/Controllers/Admin/ServiceController.php @@ -0,0 +1,145 @@ + + * + * 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 Alert; +use DB; +use Log; +use Validator; + +use Pterodactyl\Models; +use Pterodactyl\Repositories\ServiceRepository; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Exceptions\DisplayValidationException; + +use Pterodactyl\Http\Controllers\Controller; +use Illuminate\Http\Request; + +class ServiceController extends Controller +{ + + public function __construct() + { + // + } + + public function getIndex(Request $request) + { + return view('admin.services.index', [ + 'services' => Models\Service::all() + ]); + } + + public function getNew(Request $request) + { + // + } + + public function postNew(Request $request) + { + // + } + + public function getService(Request $request, $service) + { + return view('admin.services.view', [ + 'service' => Models\Service::findOrFail($service), + 'options' => Models\ServiceOptions::select( + 'service_options.*', + DB::raw('(SELECT COUNT(*) FROM servers WHERE servers.option = service_options.id) as c_servers') + )->where('parent_service', $service)->get() + ]); + } + + public function postService(Request $request, $service) + { + try { + $repo = new ServiceRepository\Service; + $repo->update($service, $request->except([ + '_token' + ])); + 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 getOption(Request $request, $option) + { + $opt = Models\ServiceOptions::findOrFail($option); + return view('admin.services.options.view', [ + 'service' => Models\Service::findOrFail($opt->parent_service), + 'option' => $opt, + 'variables' => Models\ServiceVariables::where('option_id', $option)->get(), + 'servers' => Models\Server::select('servers.*', 'users.email as a_ownerEmail') + ->join('users', 'users.id', '=', 'servers.owner') + ->where('option', $option) + ->paginate(10) + ]); + } + + public function postOption(Request $request, $option) + { + // editing option + } + + public function postOptionVariable(Request $request, $option, $variable) + { + if ($variable === 'new') { + // adding new variable + } else { + 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 = []; + 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', $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', $option)->withInput(); + } + } + +} diff --git a/app/Http/Routes/AdminRoutes.php b/app/Http/Routes/AdminRoutes.php index f4d12111..34bcf94a 100644 --- a/app/Http/Routes/AdminRoutes.php +++ b/app/Http/Routes/AdminRoutes.php @@ -334,6 +334,44 @@ class AdminRoutes { ]); }); + // Service Routes + $router->group([ + 'prefix' => 'admin/services', + 'middleware' => [ + 'auth', + 'admin', + 'csrf' + ] + ], function () use ($router) { + $router->get('/', [ + 'as' => 'admin.services', + 'uses' => 'Admin\ServiceController@getIndex' + ]); + + $router->get('/service/{id}', [ + 'as' => 'admin.services.service', + 'uses' => 'Admin\ServiceController@getService' + ]); + + $router->post('/service/{id}', [ + 'uses' => 'Admin\ServiceController@postService' + ]); + + $router->get('/option/{id}', [ + 'as' => 'admin.services.option', + 'uses' => 'Admin\ServiceController@getOption' + ]); + + $router->post('/option/{id}', [ + 'uses' => 'Admin\ServiceController@postOption' + ]); + + $router->post('/option/{option}/{variable}', [ + 'as' => 'admin.services.option.variable', + 'uses' => 'Admin\ServiceController@postOptionVariable' + ]); + }); + } } diff --git a/app/Models/Service.php b/app/Models/Service.php index 2004588f..e1d3e23e 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -35,4 +35,11 @@ class Service extends Model */ protected $table = 'services'; + /** + * Fields that are not mass assignable. + * + * @var array + */ + protected $guarded = ['id', 'created_at', 'updated_at']; + } diff --git a/app/Models/ServiceOptions.php b/app/Models/ServiceOptions.php index abe0a0dd..393445a4 100644 --- a/app/Models/ServiceOptions.php +++ b/app/Models/ServiceOptions.php @@ -35,6 +35,13 @@ class ServiceOptions extends Model */ protected $table = 'service_options'; + /** + * Fields that are not mass assignable. + * + * @var array + */ + protected $guarded = ['id', 'created_at', 'updated_at']; + /** * Cast values to correct type. * diff --git a/app/Models/ServiceVariables.php b/app/Models/ServiceVariables.php index 94399c8a..8b7f203b 100644 --- a/app/Models/ServiceVariables.php +++ b/app/Models/ServiceVariables.php @@ -35,6 +35,13 @@ class ServiceVariables extends Model */ protected $table = 'service_variables'; + /** + * Fields that are not mass assignable. + * + * @var array + */ + protected $guarded = ['id', 'created_at', 'updated_at']; + /** * Cast values to correct type. * diff --git a/app/Repositories/ServiceRepository/Option.php b/app/Repositories/ServiceRepository/Option.php new file mode 100644 index 00000000..0b4d705c --- /dev/null +++ b/app/Repositories/ServiceRepository/Option.php @@ -0,0 +1,48 @@ + + * + * 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\Services\UuidService; + +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Exceptions\DisplayValidationException; + +class Option +{ + + public function __construct() + { + // + } + + public function update($id, array $data) + { + + } + +} diff --git a/app/Repositories/ServiceRepository/Service.php b/app/Repositories/ServiceRepository/Service.php new file mode 100644 index 00000000..bd9f6a9f --- /dev/null +++ b/app/Repositories/ServiceRepository/Service.php @@ -0,0 +1,63 @@ + + * + * 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\Services\UuidService; + +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Exceptions\DisplayValidationException; + +class Service +{ + + public function __construct() + { + // + } + + 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|required|max:255|regex:/^(.*)$/', + 'startup' => 'sometimes|required|string' + ]); + + if ($validator->fails()) { + throw new DisplayValidationException($validator->errors()); + } + + $service->fill($data); + $service->save(); + } + +} diff --git a/app/Repositories/ServiceRepository/Variable.php b/app/Repositories/ServiceRepository/Variable.php new file mode 100644 index 00000000..88a21e5c --- /dev/null +++ b/app/Repositories/ServiceRepository/Variable.php @@ -0,0 +1,77 @@ + + * + * 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\Services\UuidService; + +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Exceptions\DisplayValidationException; + +class Variable +{ + + public function __construct() + { + // + } + + public function update($id, array $data) + { + $variable = Models\ServiceVariables::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|required|string', + 'user_viewable' => 'sometimes|required|numeric|size:1', + 'user_editable' => 'sometimes|required|numeric|size:1', + 'required' => 'sometimes|required|numeric|size:1', + '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 (!preg_match($data['regex'], $data['default_value'])) { + throw new DisplayException('The default value you entered cannot violate the regex requirements.'); + } + + $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; + + $variable->fill($data); + $variable->save(); + } + +} diff --git a/resources/views/admin/services/index.blade.php b/resources/views/admin/services/index.blade.php new file mode 100644 index 00000000..66b9b4e8 --- /dev/null +++ b/resources/views/admin/services/index.blade.php @@ -0,0 +1,55 @@ +{{-- Copyright (c) 2015 - 2016 Dane Everitt --}} + +{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} +{{-- of this software and associated documentation files (the "Software"), to deal --}} +{{-- in the Software without restriction, including without limitation the rights --}} +{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} +{{-- copies of the Software, and to permit persons to whom the Software is --}} +{{-- furnished to do so, subject to the following conditions: --}} + +{{-- The above copyright notice and this permission notice shall be included in all --}} +{{-- copies or substantial portions of the Software. --}} + +{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} +{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} +{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} +{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} +{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} +{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} +{{-- SOFTWARE. --}} +@extends('layouts.admin') + +@section('title') + Manage Services +@endsection + +@section('content') +
+ +

Server Services


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

Servers

+ + + + + + + + + + + @foreach ($servers as $server) + + + + + + + @endforeach + +
NameOwnerConnectionUpdated
{{ $server->name }}{{ $server->a_ownerEmail }}{{ $server->ip }}:{{ $server->port }}{{ $server->updated_at }}
+

Option Variables


+ @foreach($variables as $variable) +
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+ +
+ +

Accessed in startup by using {{{{ $variable->env_variable }}}} prameter.

+
+
+
+ +
+ +

The default value to use for this field.

+
+
+
+ +
+ +

Regex code to use when verifying the contents of the field.

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

Service Options


+ + + + + + + + + + + + @foreach($options as $option) + + + + + + + + @endforeach + +
Option NameDescriptionDocker ImageTagServers
{{ $option->name }}{!! $option->description !!}{{ $option->docker_image }}{{ $option->tag }}{{ $option->c_servers }}
+
+
+
+
+ +
+ +

This should be a descriptive category name that emcompasses all of the options within the service.

+
+
+
+ +
+ +
+
+
+
+
+ +
+ /src/services/ + + /index.js +
+

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.

+
+
+ +
+ +
+

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.

+
+
+
+
+ +
+ {{ $service->executable }} + +
+

This is the default startup that will be used for all servers created using this service. This can be changed per-option.

+
+
+
+
+ {!! csrf_field() !!} + +
+
+
+
+
+ +@endsection From 4e60adc875649c7311a6997e81940f70d79d5ad9 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 20 Feb 2016 15:59:04 -0500 Subject: [PATCH 03/19] Make file field unique --- ..._02_20_155318_add_unique_service_field.php | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 database/migrations/2016_02_20_155318_add_unique_service_field.php diff --git a/database/migrations/2016_02_20_155318_add_unique_service_field.php b/database/migrations/2016_02_20_155318_add_unique_service_field.php new file mode 100644 index 00000000..798ac4ba --- /dev/null +++ b/database/migrations/2016_02_20_155318_add_unique_service_field.php @@ -0,0 +1,31 @@ +string('file')->unique()->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('services', function (Blueprint $table) { + $table->dropUnique('services_file_unique'); + }); + } +} From e42547a1ff8b5e7df4e8c022c5da74005d3ea542 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 20 Feb 2016 15:59:37 -0500 Subject: [PATCH 04/19] add support for editing service options --- .../Controllers/Admin/ServiceController.php | 14 ++- app/Repositories/ServiceRepository/Option.php | 24 +++++ .../admin/services/options/view.blade.php | 101 ++++++++++++++---- 3 files changed, 116 insertions(+), 23 deletions(-) diff --git a/app/Http/Controllers/Admin/ServiceController.php b/app/Http/Controllers/Admin/ServiceController.php index 981a8b32..ed475660 100644 --- a/app/Http/Controllers/Admin/ServiceController.php +++ b/app/Http/Controllers/Admin/ServiceController.php @@ -107,7 +107,19 @@ class ServiceController extends Controller public function postOption(Request $request, $option) { - // editing option + try { + $repo = new ServiceRepository\Option; + $repo->update($option, $request->except([ + '_token' + ])); + Alert::success('Option settings successfully updated.')->flash(); + } catch (DisplayValidationException $ex) { + return redirect()->route('admin.services.option', $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', $option)->withInput(); } public function postOptionVariable(Request $request, $option, $variable) diff --git a/app/Repositories/ServiceRepository/Option.php b/app/Repositories/ServiceRepository/Option.php index 0b4d705c..92c4dd36 100644 --- a/app/Repositories/ServiceRepository/Option.php +++ b/app/Repositories/ServiceRepository/Option.php @@ -42,7 +42,31 @@ class Option public function update($id, array $data) { + $option = Models\ServiceOptions::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); + $option->save(); } } diff --git a/resources/views/admin/services/options/view.blade.php b/resources/views/admin/services/options/view.blade.php index 1eb51173..aca4d83e 100644 --- a/resources/views/admin/services/options/view.blade.php +++ b/resources/views/admin/services/options/view.blade.php @@ -31,28 +31,64 @@
  • {{ $service->name }}
  • {{ $option->name }}
  • -

    Servers

    - - - - - - - - - - - @foreach ($servers as $server) - - - - - - - @endforeach - -
    NameOwnerConnectionUpdated
    {{ $server->name }}{{ $server->a_ownerEmail }}{{ $server->ip }}:{{ $server->port }}{{ $server->updated_at }}
    -

    Option Variables


    +
    Warning! 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.
    +

    Settings


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

    Leave blank to use parent executable.

    +
    +
    +
    + +
    + +

    Changing the docker image will only effect servers created or modified after this point.

    +
    +
    +
    +
    +
    + +
    + +

    To use the default startup of the parent service simply leave this field blank.

    +
    +
    +
    +
    +
    +
    + {!! csrf_field() !!} + +
    +
    +
    +
    +

    Variables


    @foreach($variables as $variable)
    @@ -128,6 +164,27 @@
    @endforeach +

    Servers


    + + + + + + + + + + + @foreach ($servers as $server) + + + + + + + @endforeach + +
    NameOwnerConnectionUpdated
    {{ $server->name }}{{ $server->a_ownerEmail }}{{ $server->ip }}:{{ $server->port }}{{ $server->updated_at }}
    +@endsection From 177bd4ec9ddc29880590ffa47e6e6361ed1adb86 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 20 Feb 2016 16:23:04 -0500 Subject: [PATCH 06/19] add ability to delete a service --- .../Controllers/Admin/ServiceController.php | 16 ++++++++++++++ app/Http/Routes/AdminRoutes.php | 4 ++++ .../ServiceRepository/Service.php | 22 +++++++++++++++++++ resources/views/admin/services/view.blade.php | 12 ++++++++++ 4 files changed, 54 insertions(+) diff --git a/app/Http/Controllers/Admin/ServiceController.php b/app/Http/Controllers/Admin/ServiceController.php index e0496d1b..02d8c735 100644 --- a/app/Http/Controllers/Admin/ServiceController.php +++ b/app/Http/Controllers/Admin/ServiceController.php @@ -106,6 +106,22 @@ class ServiceController extends Controller 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(); + 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, $option) { $opt = Models\ServiceOptions::findOrFail($option); diff --git a/app/Http/Routes/AdminRoutes.php b/app/Http/Routes/AdminRoutes.php index c9a20441..e59103d5 100644 --- a/app/Http/Routes/AdminRoutes.php +++ b/app/Http/Routes/AdminRoutes.php @@ -366,6 +366,10 @@ class AdminRoutes { 'uses' => 'Admin\ServiceController@postService' ]); + $router->delete('/service/{id}', [ + 'uses' => 'Admin\ServiceController@deleteService' + ]); + $router->get('/option/{id}', [ 'as' => 'admin.services.option', 'uses' => 'Admin\ServiceController@getOption' diff --git a/app/Repositories/ServiceRepository/Service.php b/app/Repositories/ServiceRepository/Service.php index 2595c47e..84e01f3e 100644 --- a/app/Repositories/ServiceRepository/Service.php +++ b/app/Repositories/ServiceRepository/Service.php @@ -85,4 +85,26 @@ class Service $service->save(); } + public function delete($id) + { + $service = Models\Service::findOrFail($id); + $servers = Models\Server::where('service', $service->id)->get(); + $options = Models\ServiceOptions::select('id')->where('parent_service', $service->id); + + if (count($servers) !== 0) { + throw new DisplayException('You cannot delete a service that has servers associated with it.'); + } + + DB::beginTransaction(); + try { + Models\ServiceVariables::whereIn('option_id', $options->get()->toArray())->delete(); + $options->delete(); + $service->delete(); + DB::commit(); + } catch (\Exception $ex) { + DB::rollBack(); + throw $ex; + } + } + } diff --git a/resources/views/admin/services/view.blade.php b/resources/views/admin/services/view.blade.php index 9526ed5f..8ede24b6 100644 --- a/resources/views/admin/services/view.blade.php +++ b/resources/views/admin/services/view.blade.php @@ -106,6 +106,18 @@ +
    +
    +
    +
    + Deleting a service is an irreversible action. A service can only be deleted if no servers are associated with it. +
    + {!! csrf_field() !!} + {!! method_field('DELETE') !!} + +
    +
    +
    +@endsection diff --git a/resources/views/admin/services/options/view.blade.php b/resources/views/admin/services/options/view.blade.php index aca4d83e..31c55ffe 100644 --- a/resources/views/admin/services/options/view.blade.php +++ b/resources/views/admin/services/options/view.blade.php @@ -20,7 +20,7 @@ @extends('layouts.admin') @section('title') - Manage Services + Manage Service Option {{ $option->name }} @endsection @section('content') diff --git a/resources/views/admin/services/view.blade.php b/resources/views/admin/services/view.blade.php index 8ede24b6..28e560fc 100644 --- a/resources/views/admin/services/view.blade.php +++ b/resources/views/admin/services/view.blade.php @@ -20,7 +20,7 @@ @extends('layouts.admin') @section('title') - Manage Services + Manage Service @endsection @section('content') @@ -51,6 +51,13 @@ {{ $option->c_servers }} @endforeach + + + + + + +
    From dcfdb89e3ccd047629d0b8a95b2c65a13f1c2154 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 20 Feb 2016 16:55:05 -0500 Subject: [PATCH 08/19] add support for deleting service option --- .../Controllers/Admin/ServiceController.php | 18 +++++++++++++++ app/Http/Routes/AdminRoutes.php | 4 ++++ app/Repositories/ServiceRepository/Option.php | 22 +++++++++++++++++++ .../admin/services/options/view.blade.php | 12 ++++++++++ 4 files changed, 56 insertions(+) diff --git a/app/Http/Controllers/Admin/ServiceController.php b/app/Http/Controllers/Admin/ServiceController.php index 3f0670da..5ab9e96d 100644 --- a/app/Http/Controllers/Admin/ServiceController.php +++ b/app/Http/Controllers/Admin/ServiceController.php @@ -156,6 +156,24 @@ class ServiceController extends Controller return redirect()->route('admin.services.option', $option)->withInput(); } + public function deleteOption(Request $request, $option) + { + try { + $service = Models\ServiceOptions::select('parent_service')->where('id', $option)->first(); + $repo = new ServiceRepository\Option; + $repo->delete($option); + + Alert::success('Successfully deleted that option.')->flash(); + return redirect()->route('admin.services.service', $service->parent_service); + } 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(); + } + return redirect()->route('admin.services.option', $option); + } + public function postOptionVariable(Request $request, $option, $variable) { if ($variable === 'new') { diff --git a/app/Http/Routes/AdminRoutes.php b/app/Http/Routes/AdminRoutes.php index 356df760..d597d342 100644 --- a/app/Http/Routes/AdminRoutes.php +++ b/app/Http/Routes/AdminRoutes.php @@ -388,6 +388,10 @@ class AdminRoutes { 'uses' => 'Admin\ServiceController@postOption' ]); + $router->delete('/option/{id}', [ + 'uses' => 'Admin\ServiceController@deleteOption' + ]); + $router->post('/option/{option}/{variable}', [ 'as' => 'admin.services.option.variable', 'uses' => 'Admin\ServiceController@postOptionVariable' diff --git a/app/Repositories/ServiceRepository/Option.php b/app/Repositories/ServiceRepository/Option.php index 2622c3f6..60476b50 100644 --- a/app/Repositories/ServiceRepository/Option.php +++ b/app/Repositories/ServiceRepository/Option.php @@ -73,6 +73,28 @@ class Option return $option->id; } + public function delete($id) + { + $option = Models\ServiceOptions::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\ServiceVariables::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\ServiceOptions::findOrFail($id); diff --git a/resources/views/admin/services/options/view.blade.php b/resources/views/admin/services/options/view.blade.php index 31c55ffe..cea38878 100644 --- a/resources/views/admin/services/options/view.blade.php +++ b/resources/views/admin/services/options/view.blade.php @@ -185,6 +185,18 @@ @endforeach +
    +
    +
    +
    + Deleting an option is an irreversible action. An option can only be deleted if no servers are associated with it. +
    + {!! csrf_field() !!} + {!! method_field('DELETE') !!} + +
    +
    +
    +@endsection diff --git a/resources/views/admin/services/options/view.blade.php b/resources/views/admin/services/options/view.blade.php index cea38878..e4626356 100644 --- a/resources/views/admin/services/options/view.blade.php +++ b/resources/views/admin/services/options/view.blade.php @@ -33,7 +33,7 @@
    Warning! 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.

    Settings


    -
    +
    @@ -90,7 +90,7 @@

    Variables


    @foreach($variables as $variable) -
    +
    @@ -185,7 +185,7 @@ @endforeach - +
    diff --git a/resources/views/admin/services/view.blade.php b/resources/views/admin/services/view.blade.php index 28e560fc..463fadaa 100644 --- a/resources/views/admin/services/view.blade.php +++ b/resources/views/admin/services/view.blade.php @@ -44,7 +44,7 @@ @foreach($options as $option) - {{ $option->name }} + {{ $option->name }} {!! $option->description !!} {{ $option->docker_image }} {{ $option->tag }} From 48b9bc0c52abb54402f35355ca68eaa6b9125d07 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 21 Feb 2016 00:38:03 -0500 Subject: [PATCH 11/19] add support for variable creation and deletion --- .../Controllers/Admin/ServiceController.php | 43 ++++++++++ app/Http/Routes/AdminRoutes.php | 14 ++++ .../ServiceRepository/Variable.php | 58 ++++++++++++- .../admin/services/options/variable.blade.php | 82 ++++++++++++++++++- .../admin/services/options/view.blade.php | 5 +- 5 files changed, 198 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/Admin/ServiceController.php b/app/Http/Controllers/Admin/ServiceController.php index 325c1987..c6191922 100644 --- a/app/Http/Controllers/Admin/ServiceController.php +++ b/app/Http/Controllers/Admin/ServiceController.php @@ -202,6 +202,34 @@ class ServiceController extends Controller return redirect()->route('admin.services.option', [$service, $option])->withInput(); } + public function getNewVariable(Request $request, $service, $option) + { + return view('admin.services.options.variable', [ + 'service' => Models\Service::findOrFail($service), + 'option' => Models\ServiceOptions::where('parent_service', $service)->where('id', $option)->firstOrFail() + ]); + } + + public function postNewVariable(Request $request, $service, $option) + { + try { + $repo = new ServiceRepository\Variable; + $repo->create($option, $request->except([ + '_token' + ])); + Alert::success('Successfully added new variable to this option.')->flash(); + return redirect()->route('admin.services.option', [$service, $option])->withInput(); + } 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', [ @@ -227,4 +255,19 @@ class ServiceController extends Controller 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]); + } + } diff --git a/app/Http/Routes/AdminRoutes.php b/app/Http/Routes/AdminRoutes.php index 7bea4118..c4d8eb07 100644 --- a/app/Http/Routes/AdminRoutes.php +++ b/app/Http/Routes/AdminRoutes.php @@ -392,10 +392,24 @@ class AdminRoutes { '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' + ]); }); } diff --git a/app/Repositories/ServiceRepository/Variable.php b/app/Repositories/ServiceRepository/Variable.php index 88a21e5c..714c5c0e 100644 --- a/app/Repositories/ServiceRepository/Variable.php +++ b/app/Repositories/ServiceRepository/Variable.php @@ -40,6 +40,58 @@ class Variable // } + public function create($id, array $data) + { + $option = Models\ServiceOptions::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' => 'required|string|max:255', + 'user_viewable' => 'sometimes|required|numeric|size:1', + 'user_editable' => 'sometimes|required|numeric|size:1', + 'required' => 'sometimes|required|numeric|size:1', + 'regex' => 'required|string|min:1' + ]); + + if ($validator->fails()) { + throw new DisplayValidationException($validator->errors()); + } + + if (!preg_match($data['regex'], $data['default_value'])) { + throw new DisplayException('The default value you entered cannot violate the regex requirements.'); + } + + if (Models\ServiceVariables::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; + + $variable = new Models\ServiceVariables; + $variable->option_id = $option->id; + $variable->fill($data); + $variable->save(); + } + + public function delete($id) { + $variable = Models\ServiceVariables::findOrFail($id); + + DB::beginTransaction(); + try { + Models\ServerVariables::where('variable_id', $variable->id)->delete(); + $variable->delete(); + + DB::commit(); + } catch (\Exception $ex) { + DB::rollBack(); + throw $ex; + } + } + public function update($id, array $data) { $variable = Models\ServiceVariables::findOrFail($id); @@ -48,7 +100,7 @@ class Variable 'name' => 'sometimes|required|string|min:1|max:255', 'description' => 'sometimes|required|string', 'env_variable' => 'sometimes|required|regex:/^[\w]{1,255}$/', - 'default_value' => 'sometimes|required|string', + 'default_value' => 'sometimes|required|string|max:255', 'user_viewable' => 'sometimes|required|numeric|size:1', 'user_editable' => 'sometimes|required|numeric|size:1', 'required' => 'sometimes|required|numeric|size:1', @@ -66,6 +118,10 @@ class Variable throw new DisplayException('The default value you entered cannot violate the regex requirements.'); } + if (Models\ServiceVariables::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; diff --git a/resources/views/admin/services/options/variable.blade.php b/resources/views/admin/services/options/variable.blade.php index c48a4f21..9cfceea6 100644 --- a/resources/views/admin/services/options/variable.blade.php +++ b/resources/views/admin/services/options/variable.blade.php @@ -29,14 +29,94 @@
  • Admin Control
  • Services
  • {{ $service->name }}
  • -
  • {{ $option->name }}
  • +
  • {{ $option->name }}
  • New Variable
  • New Option Variable


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

    Regex code to use when verifying the contents of the field.

    +
    +
    +
    +
    +
    + +
    + +

    Accessed in startup by using {{}} parameter.

    +
    +
    +
    + +
    + +

    The default value to use for this field.

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + {!! csrf_field() !!} + +
    +
    +
    +
    @endsection diff --git a/resources/views/admin/services/options/view.blade.php b/resources/views/admin/services/options/view.blade.php index e4626356..b5bf2f54 100644 --- a/resources/views/admin/services/options/view.blade.php +++ b/resources/views/admin/services/options/view.blade.php @@ -88,7 +88,7 @@
    -

    Variables


    +

    Variables


    @foreach($variables as $variable)
    @@ -158,7 +158,8 @@
    {!! csrf_field() !!} - + +
    From f6be06164f03e50727c2824201721fc04a3aef0a Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 21 Feb 2016 01:15:37 -0500 Subject: [PATCH 12/19] fix user controller; closes #58, closes #59 --- .../Controllers/Admin/AccountsController.php | 145 --------------- app/Http/Controllers/Admin/UserController.php | 134 ++++++++++++++ app/Http/Routes/AdminRoutes.php | 40 ++-- app/Repositories/UserRepository.php | 30 +-- resources/lang/en/base.php | 1 - resources/views/admin/accounts/view.blade.php | 173 ------------------ resources/views/admin/servers/index.blade.php | 2 +- resources/views/admin/servers/view.blade.php | 2 +- .../admin/services/options/view.blade.php | 2 +- .../admin/{accounts => users}/index.blade.php | 4 +- .../admin/{accounts => users}/new.blade.php | 4 +- resources/views/admin/users/view.blade.php | 160 ++++++++++++++++ resources/views/layouts/admin.blade.php | 10 +- 13 files changed, 345 insertions(+), 362 deletions(-) delete mode 100644 app/Http/Controllers/Admin/AccountsController.php create mode 100644 app/Http/Controllers/Admin/UserController.php delete mode 100644 resources/views/admin/accounts/view.blade.php rename resources/views/admin/{accounts => users}/index.blade.php (89%) rename resources/views/admin/{accounts => users}/new.blade.php (96%) create mode 100644 resources/views/admin/users/view.blade.php diff --git a/app/Http/Controllers/Admin/AccountsController.php b/app/Http/Controllers/Admin/AccountsController.php deleted file mode 100644 index 4bbf4c08..00000000 --- a/app/Http/Controllers/Admin/AccountsController.php +++ /dev/null @@ -1,145 +0,0 @@ - - * Some Modifications (c) 2015 Dylan Seidt - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -namespace Pterodactyl\Http\Controllers\Admin; - -use Alert; -use Settings; -use Mail; -use Log; -use Pterodactyl\Models\User; -use Pterodactyl\Repositories\UserRepository; -use Pterodactyl\Models\Server; - -use Pterodactyl\Http\Controllers\Controller; -use Illuminate\Http\Request; - -class AccountsController extends Controller -{ - - /** - * Controller Constructor - */ - public function __construct() - { - // - } - - public function getIndex(Request $request) - { - return view('admin.accounts.index', [ - 'users' => User::paginate(20) - ]); - } - - public function getNew(Request $request) - { - return view('admin.accounts.new'); - } - - public function getView(Request $request, $id) - { - return view('admin.accounts.view', [ - 'user' => User::findOrFail($id), - 'servers' => Server::select('servers.*', 'nodes.name as nodeName', 'locations.long as location') - ->join('nodes', 'servers.node', '=', 'nodes.id') - ->join('locations', 'nodes.location', '=', 'locations.id') - ->where('owner', $id) - ->where('active', 1) - ->get(), - ]); - } - - public function deleteView(Request $request, $id) - { - try { - User::findOrFail($id)->delete(); - return response(null, 204); - } catch(\Exception $ex) { - Log::error($ex); - return response()->json([ - 'error' => 'An error occured while attempting to delete this user.' - ], 500); - } - } - - public function postNew(Request $request) - { - try { - $user = new UserRepository; - $userid = $user->create($request->input('email'), $request->input('password')); - Alert::success('Account has been successfully created.')->flash(); - return redirect()->route('admin.accounts.view', ['id' => $userid]); - } catch (\Pterodactyl\Exceptions\DisplayValidationException $ex) { - return redirect()->route('admin.accounts.new')->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An error occured while attempting to add a new user. ' . $ex->getMessage())->flash(); - return redirect()->route('admin.accounts.new'); - } - } - - public function postUpdate(Request $request) - { - $this->validate($request, [ - 'email' => 'required|email|unique:users,email,'.$request->input('user'), - 'root_admin' => 'required', - 'password' => 'required_with:password_confirmation|confirmed', - 'password_confirmation' => 'required_with:password' - ]); - - try { - - $users = new UserRepository; - $user = [ - 'email' => $request->input('email'), - 'root_admin' => $request->input('root_admin') - ]; - - if(!empty($request->input('password'))) { - $user['password'] = $request->input('password'); - } - - if(!$users->update($request->input('user'), $user)) { - throw new \Exception('Unable to update user, response was not valid.'); - } - - if($request->input('email_user')) { - Mail::queue('emails.new_password', ['user' => User::findOrFail($request->input('user')), 'password' => $request->input('password')], function($message) use ($request) { - $message->to($request->input('email'))->subject(Settings::get('company') . ' - Admin Reset Password'); - $message->from(Settings::get('email_from', env('MAIL_FROM')), Settings::get('email_sender_name', env('MAIL_FROM_NAME', 'Pterodactyl Panel'))); - }); - } - - Alert::success('User account was successfully updated.')->flash(); - return redirect()->route('admin.accounts.view', ['id' => $request->input('user')]); - - } catch (\Exception $e) { - Log::error($e); - Alert::danger('An error occured while attempting to update this user. ' . $e->getMessage())->flash(); - return redirect()->route('admin.accounts.view', ['id' => $request->input('user')]); - } - } - -} diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php new file mode 100644 index 00000000..da0aeac9 --- /dev/null +++ b/app/Http/Controllers/Admin/UserController.php @@ -0,0 +1,134 @@ + + * Some Modifications (c) 2015 Dylan Seidt + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +namespace Pterodactyl\Http\Controllers\Admin; + +use Alert; +use Settings; +use Mail; +use Log; +use Pterodactyl\Models\User; +use Pterodactyl\Repositories\UserRepository; +use Pterodactyl\Models\Server; + +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Exceptions\DisplayValidationException; + +use Pterodactyl\Http\Controllers\Controller; +use Illuminate\Http\Request; + +class UserController extends Controller +{ + + /** + * Controller Constructor + */ + public function __construct() + { + // + } + + public function getIndex(Request $request) + { + return view('admin.users.index', [ + 'users' => User::paginate(20) + ]); + } + + public function getNew(Request $request) + { + return view('admin.users.new'); + } + + public function getView(Request $request, $id) + { + return view('admin.users.view', [ + 'user' => User::findOrFail($id), + 'servers' => Server::select('servers.*', 'nodes.name as nodeName', 'locations.long as location') + ->join('nodes', 'servers.node', '=', 'nodes.id') + ->join('locations', 'nodes.location', '=', 'locations.id') + ->where('owner', $id) + ->where('active', 1) + ->get(), + ]); + } + + public function deleteUser(Request $request, $id) + { + try { + $repo = new UserRepository; + $repo->delete($id); + Alert::success('Successfully deleted user from system.')->flash(); + return redirect()->route('admin.users'); + } catch(DisplayException $ex) { + Alert::danger($ex->getMessage())->flash(); + } catch (\Exception $ex) { + Log::error($ex); + Alert::danger('An exception was encountered while attempting to delete this user.')->flash(); + } + return redirect()->route('admin.users.view', $id); + } + + public function postNew(Request $request) + { + try { + $user = new UserRepository; + $userid = $user->create($request->input('email'), $request->input('password')); + Alert::success('Account has been successfully created.')->flash(); + return redirect()->route('admin.users.view', $userid); + } catch (DisplayValidationException $ex) { + return redirect()->route('admin.users.new')->withErrors(json_decode($ex->getMessage()))->withInput(); + } catch (\Exception $ex) { + Log::error($ex); + Alert::danger('An error occured while attempting to add a new user.')->flash(); + return redirect()->route('admin.users.new'); + } + } + + public function updateUser(Request $request, $user) + { + $data = [ + 'email' => $request->input('email'), + 'root_admin' => $request->input('root_admin'), + 'password_confirmation' => $request->input('password_confirmation'), + ]; + + if ($request->input('password')) { + $data['password'] = $request->input('password'); + } + + try { + $repo = new UserRepository; + $repo->update($user, $data); + Alert::success('User account was successfully updated.')->flash(); + } catch (DisplayValidationException $ex) { + return redirect()->route('admin.users.view', $user)->withErrors(json_decode($ex->getMessage())); + } catch (\Exception $e) { + Log::error($e); + Alert::danger('An error occured while attempting to update this user.')->flash(); + } + return redirect()->route('admin.users.view', $user); + } + +} diff --git a/app/Http/Routes/AdminRoutes.php b/app/Http/Routes/AdminRoutes.php index c4d8eb07..53f1a23a 100644 --- a/app/Http/Routes/AdminRoutes.php +++ b/app/Http/Routes/AdminRoutes.php @@ -59,7 +59,7 @@ class AdminRoutes { }); $router->group([ - 'prefix' => 'admin/accounts', + 'prefix' => 'admin/users', 'middleware' => [ 'auth', 'admin', @@ -69,35 +69,35 @@ class AdminRoutes { // View All Accounts on System $router->get('/', [ - 'as' => 'admin.accounts', - 'uses' => 'Admin\AccountsController@getIndex' + 'as' => 'admin.users', + 'uses' => 'Admin\UserController@getIndex' ]); // View Specific Account $router->get('/view/{id}', [ - 'as' => 'admin.accounts.view', - 'uses' => 'Admin\AccountsController@getView' + 'as' => 'admin.users.view', + 'uses' => 'Admin\UserController@getView' ]); - // Show Create Account Page - $router->get('/new', [ - 'as' => 'admin.accounts.new', - 'uses' => 'Admin\AccountsController@getNew' - ]); - - // Handle Creating New Account - $router->post('/new', [ - 'uses' => 'Admin\AccountsController@postNew' - ]); - - // Update A Specific Account - $router->post('/update', [ - 'uses' => 'Admin\AccountsController@postUpdate' + // View Specific Account + $router->post('/view/{id}', [ + 'uses' => 'Admin\UserController@updateUser' ]); // Delete an Account Matching an ID $router->delete('/view/{id}', [ - 'uses' => 'Admin\AccountsController@deleteView' + 'uses' => 'Admin\UserController@deleteUser' + ]); + + // Show Create Account Page + $router->get('/new', [ + 'as' => 'admin.users.new', + 'uses' => 'Admin\UserController@getNew' + ]); + + // Handle Creating New Account + $router->post('/new', [ + 'uses' => 'Admin\UserController@postNew' ]); }); diff --git a/app/Repositories/UserRepository.php b/app/Repositories/UserRepository.php index d17732b5..c2be3b6b 100644 --- a/app/Repositories/UserRepository.php +++ b/app/Repositories/UserRepository.php @@ -108,13 +108,15 @@ class UserRepository */ public function update($id, array $data) { + $user = Models\User::findOrFail($id); + $validator = Validator::make($data, [ - 'email' => 'email|unique:users,email,' . $id, - 'password' => 'regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})', - 'root_admin' => 'boolean', - 'language' => 'string|min:1|max:5', - 'use_totp' => 'boolean', - 'totp_secret' => 'size:16' + 'email' => 'sometimes|required|email|unique:users,email,' . $id, + 'password' => 'sometimes|required|regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})', + 'root_admin' => 'sometimes|required|boolean', + 'language' => 'sometimes|required|string|min:1|max:5', + 'use_totp' => 'sometimes|required|boolean', + 'totp_secret' => 'sometimes|required|size:16' ]); // Run validator, throw catchable and displayable exception if it fails. @@ -127,7 +129,12 @@ class UserRepository $data['password'] = Hash::make($data['password']); } - return Models\User::findOrFail($id)->update($data); + if (isset($data['password_confirmation'])) { + unset($data['password_confirmation']); + } + + $user->fill($data); + $user->save(); } /** @@ -144,14 +151,15 @@ class UserRepository DB::beginTransaction(); - Models\Permission::where('user_id', $id)->delete(); - Models\Subuser::where('user_id', $id)->delete(); - Models\User::destroy($id); - try { + Models\Permission::where('user_id', $id)->delete(); + Models\Subuser::where('user_id', $id)->delete(); + Models\User::destroy($id); + DB::commit(); return true; } catch (\Exception $ex) { + DB::rollBack(); throw $ex; } } diff --git a/resources/lang/en/base.php b/resources/lang/en/base.php index 50c0974f..bb83b961 100644 --- a/resources/lang/en/base.php +++ b/resources/lang/en/base.php @@ -44,7 +44,6 @@ return [ 'no_servers' => 'You do not currently have any servers listed on your account.', 'form_error' => 'The following errors were encountered while trying to process this request.', 'password_req' => 'Passwords must meet the following requirements: at least one uppercase character, one lowercase character, one digit, and be at least 8 characters in length.', - 'root_administrator' => 'Setting this to "Yes" gives a user full administrative access to PufferPanel.', 'account' => [ 'totp_header' => 'Two-Factor Authentication', diff --git a/resources/views/admin/accounts/view.blade.php b/resources/views/admin/accounts/view.blade.php deleted file mode 100644 index f82ebaf3..00000000 --- a/resources/views/admin/accounts/view.blade.php +++ /dev/null @@ -1,173 +0,0 @@ -{{-- Copyright (c) 2015 - 2016 Dane Everitt --}} -{{-- Some Modifications (c) 2015 Dylan Seidt --}} - -{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} -{{-- of this software and associated documentation files (the "Software"), to deal --}} -{{-- in the Software without restriction, including without limitation the rights --}} -{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} -{{-- copies of the Software, and to permit persons to whom the Software is --}} -{{-- furnished to do so, subject to the following conditions: --}} - -{{-- The above copyright notice and this permission notice shall be included in all --}} -{{-- copies or substantial portions of the Software. --}} - -{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} -{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} -{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} -{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} -{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} -{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} -{{-- SOFTWARE. --}} -@extends('layouts.admin') - -@section('title') - Viewing User -@endsection - -@section('content') -
    - -

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


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

    {{ trans('base.root_administrator') }}

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

    {{ trans('base.account.update_pass') }}


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

    Associated Servers


    - @if($servers) - - - - - - - - - - - - @foreach($servers as $server) - - - - - - - - @endforeach - -
    Server NameNodeConnection
    {{ $server->name }}{{ $server->nodeName }}{{ $server->ip }}:{{ $server->port }}@if($server->active)Enabled@elseDisabled@endif
    - @else -
    There are no servers associated with this account.
    - @endif - -
    -
    -
    -
    - -@endsection diff --git a/resources/views/admin/servers/index.blade.php b/resources/views/admin/servers/index.blade.php index 59d558c6..927c23a5 100644 --- a/resources/views/admin/servers/index.blade.php +++ b/resources/views/admin/servers/index.blade.php @@ -44,7 +44,7 @@ @foreach ($servers as $server) {{ $server->name }} - {{ $server->a_ownerEmail }} + {{ $server->a_ownerEmail }} {{ $server->a_nodeName }} {{ $server->ip }}:{{ $server->port }} {{ $server->username }} diff --git a/resources/views/admin/servers/view.blade.php b/resources/views/admin/servers/view.blade.php index 9aba9f00..878714ff 100644 --- a/resources/views/admin/servers/view.blade.php +++ b/resources/views/admin/servers/view.blade.php @@ -65,7 +65,7 @@ Owner - {{ $server->a_ownerEmail }} + {{ $server->a_ownerEmail }} Location diff --git a/resources/views/admin/services/options/view.blade.php b/resources/views/admin/services/options/view.blade.php index b5bf2f54..b405d751 100644 --- a/resources/views/admin/services/options/view.blade.php +++ b/resources/views/admin/services/options/view.blade.php @@ -179,7 +179,7 @@ @foreach ($servers as $server) {{ $server->name }} - {{ $server->a_ownerEmail }} + {{ $server->a_ownerEmail }} {{ $server->ip }}:{{ $server->port }} {{ $server->updated_at }} diff --git a/resources/views/admin/accounts/index.blade.php b/resources/views/admin/users/index.blade.php similarity index 89% rename from resources/views/admin/accounts/index.blade.php rename to resources/views/admin/users/index.blade.php index e00b9b53..4092caa6 100644 --- a/resources/views/admin/accounts/index.blade.php +++ b/resources/views/admin/users/index.blade.php @@ -42,7 +42,7 @@ @foreach ($users as $user) - {{ $user->email }} @if($user->root_admin === 1)Administrator@endif + {{ $user->email }} @if($user->root_admin === 1)Administrator@endif {{ $user->created_at }} {{ $user->updated_at }} @@ -55,7 +55,7 @@
    @endsection diff --git a/resources/views/admin/accounts/new.blade.php b/resources/views/admin/users/new.blade.php similarity index 96% rename from resources/views/admin/accounts/new.blade.php rename to resources/views/admin/users/new.blade.php index 8979dc20..1429752c 100644 --- a/resources/views/admin/accounts/new.blade.php +++ b/resources/views/admin/users/new.blade.php @@ -28,7 +28,7 @@

    Create New Account


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

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


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

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

    +
    +
    +
    + {!! csrf_field() !!} + +
    +
    +
    +
    +
    +

    {{ trans('base.account.update_pass') }}


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

    Associated Servers


    + @if($servers) + + + + + + + + + + + + @foreach($servers as $server) + + + + + + + + @endforeach + +
    Server NameNodeConnection
    {{ $server->name }}{{ $server->nodeName }}{{ $server->ip }}:{{ $server->port }}@if($server->active)Enabled@elseDisabled@endif
    + @else +
    There are no servers associated with this account.
    + @endif + +
    +
    +
    +
    +

    Delete Account


    +
    Warning! There most be no servers associated with this account in order for it to be deleted.
    +
    + {!! method_field('DELETE') !!} + {!! csrf_field() !!} + +
    +
    +
    +
    + +@endsection diff --git a/resources/views/layouts/admin.blade.php b/resources/views/layouts/admin.blade.php index adf82e14..2550ac0e 100644 --- a/resources/views/layouts/admin.blade.php +++ b/resources/views/layouts/admin.blade.php @@ -65,10 +65,10 @@
    Server Management From cad5b8c78d15dc8ade9e4a83bf364009bf26ab91 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 21 Feb 2016 01:18:30 -0500 Subject: [PATCH 13/19] allow unlimited memory for server creation; closes #60 --- app/Repositories/ServerRepository.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/Repositories/ServerRepository.php b/app/Repositories/ServerRepository.php index 2f040191..7cafb6da 100644 --- a/app/Repositories/ServerRepository.php +++ b/app/Repositories/ServerRepository.php @@ -90,11 +90,11 @@ class ServerRepository 'owner' => 'required|email|exists:users,email', 'node' => 'required|numeric|min:1|exists:nodes,id', 'name' => 'required|regex:/^([\w -]{4,35})$/', - 'memory' => 'required|numeric|min:1', - 'swap' => 'required|numeric|min:0', - 'disk' => 'required|numeric|min:1', - 'cpu' => 'required|numeric|min:0', + 'memory' => 'required|numeric|min:0', + 'swap' => 'required|numeric|min:-1', 'io' => 'required|numeric|min:10|max:1000', + 'cpu' => 'required|numeric|min:0', + 'disk' => 'required|numeric|min:0', 'ip' => 'required|ip', 'port' => 'required|numeric|min:1|max:65535', 'service' => 'required|numeric|min:1|exists:services,id', From e81545f8322e2a0ff87d0800cd1008b74ab1924a Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Thu, 25 Feb 2016 23:32:17 -0500 Subject: [PATCH 14/19] should fix timezone bug, closes #63 --- .env.example | 1 + config/app.php | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index 0b1e3d08..937baa66 100644 --- a/.env.example +++ b/.env.example @@ -2,6 +2,7 @@ APP_ENV=production APP_DEBUG=false APP_KEY=SomeRandomString3232RandomString APP_THEME=default +APP_TIMEZONE=UTC DB_HOST=localhost DB_PORT=3306 diff --git a/config/app.php b/config/app.php index 7b48ab0a..14e16eff 100644 --- a/config/app.php +++ b/config/app.php @@ -30,7 +30,7 @@ return [ | */ - 'url' => ENV('APP_URL', 'http://localhost'), + 'url' => env('APP_URL', 'http://localhost'), /* |-------------------------------------------------------------------------- @@ -43,7 +43,7 @@ return [ | */ - 'timezone' => ENV('APP_TIMEZONE', 'UTC'), + 'timezone' => env('APP_TIMEZONE', 'UTC'), /* |-------------------------------------------------------------------------- From 82a43bbf151c1485ce409b20ba8e1d0752ae2d7f Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Thu, 25 Feb 2016 23:57:23 -0500 Subject: [PATCH 15/19] update jquery --- public/js/jquery.min.js | 10 +++++----- public/js/jquery.min.map | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) create mode 100644 public/js/jquery.min.map diff --git a/public/js/jquery.min.js b/public/js/jquery.min.js index fad9ab12..7d4e12de 100644 --- a/public/js/jquery.min.js +++ b/public/js/jquery.min.js @@ -1,5 +1,5 @@ -/*! jQuery v2.1.4 | (c) 2005, 2015 jQuery Foundation, Inc. | jquery.org/license */ -!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l=a.document,m="2.1.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)+1>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=l.createElement("script"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b="length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,aa=/[+~]/,ba=/'|\\/g,ca=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),da=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ea=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fa){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(ba,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+ra(o[l]);w=aa.test(a)&&pa(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function pa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener("unload",ea,!1):e.attachEvent&&e.attachEvent("onunload",ea)),p=!f(g),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(g.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?la(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ca,da),a[3]=(a[3]||a[4]||a[5]||"").replace(ca,da),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ca,da).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(Q," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(ca,da),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return W.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(ca,da).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:oa(function(){return[0]}),last:oa(function(a,b){return[b-1]}),eq:oa(function(a,b,c){return[0>c?c+b:c]}),even:oa(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:oa(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:oa(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:oa(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function sa(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function ta(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ua(a,b,c){for(var d=0,e=b.length;e>d;d++)ga(a,b[d],c);return c}function va(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wa(a,b,c,d,e,f){return d&&!d[u]&&(d=wa(d)),e&&!e[u]&&(e=wa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ua(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:va(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=va(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=va(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sa(function(a){return a===b},h,!0),l=sa(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sa(ta(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wa(i>1&&ta(m),i>1&&ra(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xa(a.slice(i,e)),f>e&&xa(a=a.slice(e)),f>e&&ra(a))}m.push(c)}return ta(m)}function ya(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=va(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&ga.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,ya(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ca,da),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ca,da),aa.test(j[0].type)&&pa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&ra(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,aa.test(a)&&pa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler("ready"),n(l).off("ready"))))}});function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),"complete"===l.readyState?setTimeout(n.ready):(l.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+K.uid++}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){ -return M.access(a,b,c)},removeData:function(a,b){M.remove(a,b)},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthx",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,ba=/<([\w:]+)/,ca=/<|&#?\w+;/,da=/<(?:script|style|link)/i,ea=/checked\s*(?:[^=]|=\s*.checked.)/i,fa=/^$|\/(?:java|ecma)script/i,ga=/^true\/(.*)/,ha=/^\s*\s*$/g,ia={option:[1,""],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};ia.optgroup=ia.option,ia.tbody=ia.tfoot=ia.colgroup=ia.caption=ia.thead,ia.th=ia.td;function ja(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function ka(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function la(a){var b=ga.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function ma(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function na(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function oa(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pa(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=oa(h),f=oa(a),d=0,e=f.length;e>d;d++)pa(f[d],g[d]);if(b)if(c)for(f=f||oa(a),g=g||oa(h),d=0,e=f.length;e>d;d++)na(f[d],g[d]);else na(a,h);return g=oa(h,"script"),g.length>0&&ma(g,!i&&oa(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(ca.test(e)){f=f||k.appendChild(b.createElement("div")),g=(ba.exec(e)||["",""])[1].toLowerCase(),h=ia[g]||ia._default,f.innerHTML=h[1]+e.replace(aa,"<$1>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=oa(k.appendChild(e),"script"),i&&ma(f),c)){j=0;while(e=f[j++])fa.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(oa(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&ma(oa(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(oa(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!da.test(a)&&!ia[(ba.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(aa,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(oa(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(oa(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&ea.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(oa(c,"script"),ka),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,oa(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,la),j=0;g>j;j++)h=f[j],fa.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(ha,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qa,ra={};function sa(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function ta(a){var b=l,c=ra[a];return c||(c=sa(a,b),"none"!==c&&c||(qa=(qa||n("