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 4456d8e54..c66781332 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 000000000..981a8b326 --- /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 f4d12111c..34bcf94a9 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 2004588f5..e1d3e23e6 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 abe0a0dd2..393445a48 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 94399c8a4..8b7f203b3 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 000000000..0b4d705c8 --- /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 000000000..bd9f6a9f4 --- /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 000000000..88a21e5c9 --- /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 000000000..66b9b4e81 --- /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 000000000..e69de29bb 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 000000000..e69de29bb 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 000000000..1eb51173f --- /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 000000000..9526ed5ff --- /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 000000000..798ac4ba9 --- /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 981a8b326..ed475660a 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 0b4d705c8..92c4dd369 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 1eb51173f..aca4d83e1 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 e0496d1b0..02d8c7353 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 c9a20441b..e59103d5a 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 2595c47ea..84e01f3e4 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 9526ed5ff..8ede24b62 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 aca4d83e1..31c55ffec 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 8ede24b62..28e560fc5 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 3f0670dae..5ab9e96df 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 356df760c..d597d342b 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 2622c3f6d..60476b50e 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 31c55ffec..cea388786 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 cea388786..e4626356b 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 28e560fc5..463fadaa8 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 325c1987d..c61919220 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 7bea4118a..c4d8eb074 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 88a21e5c9..714c5c0eb 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 c48a4f21e..9cfceea60 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 e4626356b..b5bf2f548 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 4bbf4c085..000000000 --- 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 000000000..da0aeac9b --- /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 c4d8eb074..53f1a23a4 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 d17732b5e..c2be3b6b0 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 50c0974fe..bb83b9611 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 f82ebaf3a..000000000 --- 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 59d558c60..927c23a5a 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 9aba9f005..878714ff2 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 b5bf2f548..b405d751d 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 e00b9b53c..4092caa6c 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 8979dc209..1429752c3 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 000000000..64b02a831 --- /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 adf82e146..2550ac0e6 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 2f040191d..7cafb6daf 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 0b1e3d083..937baa664 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 7b48ab0a3..14e16eff0 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 fad9ab123..7d4e12de0 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("