From fe849789a2b1406b82b4993a31fe1c0626722a2b Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Fri, 25 Sep 2020 20:32:10 -0700 Subject: [PATCH 01/71] Dont display server usage as red if unlimited; closes #2417 --- .../components/dashboard/ServerRow.tsx | 79 +++++++------------ 1 file changed, 29 insertions(+), 50 deletions(-) diff --git a/resources/scripts/components/dashboard/ServerRow.tsx b/resources/scripts/components/dashboard/ServerRow.tsx index b9979858a..cc57870d4 100644 --- a/resources/scripts/components/dashboard/ServerRow.tsx +++ b/resources/scripts/components/dashboard/ServerRow.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { memo, useEffect, useRef, useState } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faEthernet, faHdd, faMemory, faMicrochip, faServer } from '@fortawesome/free-solid-svg-icons'; import { Link } from 'react-router-dom'; @@ -8,14 +8,29 @@ import { bytesToHuman, megabytesToHuman } from '@/helpers'; import tw from 'twin.macro'; import GreyRowBox from '@/components/elements/GreyRowBox'; import Spinner from '@/components/elements/Spinner'; +import styled from 'styled-components/macro'; +import { IconProp } from '@fortawesome/fontawesome-svg-core'; +import isEqual from 'react-fast-compare'; // Determines if the current value is in an alarm threshold so we can show it in red rather // than the more faded default style. -const isAlarmState = (current: number, limit: number): boolean => { - const limitInBytes = limit * 1024 * 1024; +const isAlarmState = (current: number, limit: number): boolean => limit > 0 && (current / (limit * 1024 * 1024) >= 0.90); - return current / limitInBytes >= 0.90; -}; +interface IconProps { + icon: IconProp; + usage: number; + limit: string; + isAlarm: boolean; +} + +const Icon = memo(styled(FontAwesomeIcon)<{ $alarm: boolean }>` + ${props => props.$alarm ? tw`text-red-400` : tw`text-neutral-500`}; +`, isEqual); + +const IconDescription = styled.p<{ $alarm: boolean }>` + ${tw`text-sm ml-2`}; + ${props => props.$alarm ? tw`text-white` : tw`text-neutral-400`}; +`; export default ({ server, className }: { server: Server; className?: string }) => { const interval = useRef(null); @@ -96,62 +111,26 @@ export default ({ server, className }: { server: Server; className?: string }) = :
- -

+ + {bytesToHuman(stats.memoryUsageInBytes)} -

+

of {memorylimit}

- -

+ + {bytesToHuman(stats.diskUsageInBytes)} -

+

of {disklimit}

From 4f21fa85f879b4dc05effe90d279767333b18bef Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Fri, 25 Sep 2020 20:44:40 -0700 Subject: [PATCH 02/71] finish fixing folder names when lots of slashes exist; closes #2377 --- .../scripts/components/server/files/NewDirectoryButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/scripts/components/server/files/NewDirectoryButton.tsx b/resources/scripts/components/server/files/NewDirectoryButton.tsx index 987fe0dad..e1f92e0e7 100644 --- a/resources/scripts/components/server/files/NewDirectoryButton.tsx +++ b/resources/scripts/components/server/files/NewDirectoryButton.tsx @@ -24,7 +24,7 @@ const schema = object().shape({ const generateDirectoryData = (name: string): FileObject => ({ key: `dir_${name.split('/', 1)[0] ?? name}`, - name: name.split('/', 1)[0] ?? name, + name: name.replace(/^(\/*)/, '').split('/', 1)[0] ?? name, mode: '0644', size: 0, isFile: false, From cc6f98c0fd4ac9e23a5fcb102f2f0e4eec5ea490 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 26 Sep 2020 16:29:26 -0700 Subject: [PATCH 03/71] Replace all instances of 255 as a max length with 191; ref #2421 --- app/Http/Requests/Admin/Egg/EggFormRequest.php | 6 +++--- app/Http/Requests/Admin/Egg/EggVariableFormRequest.php | 4 ++-- app/Http/Requests/Admin/Nest/StoreNestFormRequest.php | 2 +- app/Http/Requests/Admin/Node/AllocationFormRequest.php | 2 +- .../Admin/Settings/AdvancedSettingsFormRequest.php | 4 ++-- .../Admin/Settings/BaseSettingsFormRequest.php | 2 +- .../Admin/Settings/MailSettingsFormRequest.php | 6 +++--- .../Application/Allocations/StoreAllocationRequest.php | 2 +- .../Api/Client/Servers/Backups/StoreBackupRequest.php | 2 +- .../Client/Servers/Subusers/StoreSubuserRequest.php | 2 +- app/Models/DatabaseHost.php | 2 +- app/Models/Egg.php | 6 +++--- app/Models/EggVariable.php | 4 ++-- app/Models/Location.php | 2 +- app/Models/Mount.php | 2 +- app/Models/Nest.php | 2 +- app/Models/Schedule.php | 2 +- app/Models/Server.php | 4 ++-- app/Models/Setting.php | 2 +- app/Models/User.php | 10 +++++----- .../components/server/backups/CreateBackupButton.tsx | 2 +- .../components/server/users/EditSubuserModal.tsx | 5 ++++- resources/views/admin/locations/index.blade.php | 2 +- resources/views/admin/mounts/index.blade.php | 2 +- 24 files changed, 41 insertions(+), 38 deletions(-) diff --git a/app/Http/Requests/Admin/Egg/EggFormRequest.php b/app/Http/Requests/Admin/Egg/EggFormRequest.php index 6fe577cac..bda0e8c4d 100644 --- a/app/Http/Requests/Admin/Egg/EggFormRequest.php +++ b/app/Http/Requests/Admin/Egg/EggFormRequest.php @@ -19,12 +19,12 @@ class EggFormRequest extends AdminFormRequest public function rules() { $rules = [ - 'name' => 'required|string|max:255', + 'name' => 'required|string|max:191', 'description' => 'nullable|string', - 'docker_image' => 'required|string|max:255', + 'docker_image' => 'required|string|max:191', 'startup' => 'required|string', 'config_from' => 'sometimes|bail|nullable|numeric', - 'config_stop' => 'required_without:config_from|nullable|string|max:255', + 'config_stop' => 'required_without:config_from|nullable|string|max:191', 'config_startup' => 'required_without:config_from|nullable|json', 'config_logs' => 'required_without:config_from|nullable|json', 'config_files' => 'required_without:config_from|nullable|json', diff --git a/app/Http/Requests/Admin/Egg/EggVariableFormRequest.php b/app/Http/Requests/Admin/Egg/EggVariableFormRequest.php index 933bf8348..d52fe94d2 100644 --- a/app/Http/Requests/Admin/Egg/EggVariableFormRequest.php +++ b/app/Http/Requests/Admin/Egg/EggVariableFormRequest.php @@ -15,9 +15,9 @@ class EggVariableFormRequest extends AdminFormRequest public function rules() { return [ - 'name' => 'required|string|min:1|max:255', + 'name' => 'required|string|min:1|max:191', 'description' => 'sometimes|nullable|string', - 'env_variable' => 'required|regex:/^[\w]{1,255}$/|notIn:' . EggVariable::RESERVED_ENV_NAMES, + 'env_variable' => 'required|regex:/^[\w]{1,191}$/|notIn:' . EggVariable::RESERVED_ENV_NAMES, 'options' => 'sometimes|required|array', 'rules' => 'bail|required|string', 'default_value' => 'present', diff --git a/app/Http/Requests/Admin/Nest/StoreNestFormRequest.php b/app/Http/Requests/Admin/Nest/StoreNestFormRequest.php index 333723578..2f01dfe9e 100644 --- a/app/Http/Requests/Admin/Nest/StoreNestFormRequest.php +++ b/app/Http/Requests/Admin/Nest/StoreNestFormRequest.php @@ -19,7 +19,7 @@ class StoreNestFormRequest extends AdminFormRequest public function rules() { return [ - 'name' => 'required|string|min:1|max:255', + 'name' => 'required|string|min:1|max:191', 'description' => 'string|nullable', ]; } diff --git a/app/Http/Requests/Admin/Node/AllocationFormRequest.php b/app/Http/Requests/Admin/Node/AllocationFormRequest.php index 777d3033f..3c580c026 100644 --- a/app/Http/Requests/Admin/Node/AllocationFormRequest.php +++ b/app/Http/Requests/Admin/Node/AllocationFormRequest.php @@ -20,7 +20,7 @@ class AllocationFormRequest extends AdminFormRequest { return [ 'allocation_ip' => 'required|string', - 'allocation_alias' => 'sometimes|nullable|string|max:255', + 'allocation_alias' => 'sometimes|nullable|string|max:191', 'allocation_ports' => 'required|array', ]; } diff --git a/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php b/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php index a80d8dab9..8568e5013 100644 --- a/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php +++ b/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php @@ -15,8 +15,8 @@ class AdvancedSettingsFormRequest extends AdminFormRequest { return [ 'recaptcha:enabled' => 'required|in:true,false', - 'recaptcha:secret_key' => 'required|string|max:255', - 'recaptcha:website_key' => 'required|string|max:255', + 'recaptcha:secret_key' => 'required|string|max:191', + 'recaptcha:website_key' => 'required|string|max:191', 'pterodactyl:guzzle:timeout' => 'required|integer|between:1,60', 'pterodactyl:guzzle:connect_timeout' => 'required|integer|between:1,60', 'pterodactyl:console:count' => 'required|integer|min:1', diff --git a/app/Http/Requests/Admin/Settings/BaseSettingsFormRequest.php b/app/Http/Requests/Admin/Settings/BaseSettingsFormRequest.php index 777761b67..208c15b10 100644 --- a/app/Http/Requests/Admin/Settings/BaseSettingsFormRequest.php +++ b/app/Http/Requests/Admin/Settings/BaseSettingsFormRequest.php @@ -16,7 +16,7 @@ class BaseSettingsFormRequest extends AdminFormRequest public function rules() { return [ - 'app:name' => 'required|string|max:255', + 'app:name' => 'required|string|max:191', 'pterodactyl:auth:2fa_required' => 'required|integer|in:0,1,2', 'app:locale' => ['required', 'string', Rule::in(array_keys($this->getAvailableLanguages()))], 'app:analytics' => 'nullable|string', diff --git a/app/Http/Requests/Admin/Settings/MailSettingsFormRequest.php b/app/Http/Requests/Admin/Settings/MailSettingsFormRequest.php index 92a23272f..728283af4 100644 --- a/app/Http/Requests/Admin/Settings/MailSettingsFormRequest.php +++ b/app/Http/Requests/Admin/Settings/MailSettingsFormRequest.php @@ -18,10 +18,10 @@ class MailSettingsFormRequest extends AdminFormRequest 'mail:host' => 'required|string', 'mail:port' => 'required|integer|between:1,65535', 'mail:encryption' => ['present', Rule::in([null, 'tls', 'ssl'])], - 'mail:username' => 'nullable|string|max:255', - 'mail:password' => 'nullable|string|max:255', + 'mail:username' => 'nullable|string|max:191', + 'mail:password' => 'nullable|string|max:191', 'mail:from:address' => 'required|string|email', - 'mail:from:name' => 'nullable|string|max:255', + 'mail:from:name' => 'nullable|string|max:191', ]; } diff --git a/app/Http/Requests/Api/Application/Allocations/StoreAllocationRequest.php b/app/Http/Requests/Api/Application/Allocations/StoreAllocationRequest.php index f795a114e..1e68b82e5 100644 --- a/app/Http/Requests/Api/Application/Allocations/StoreAllocationRequest.php +++ b/app/Http/Requests/Api/Application/Allocations/StoreAllocationRequest.php @@ -24,7 +24,7 @@ class StoreAllocationRequest extends ApplicationApiRequest { return [ 'ip' => 'required|string', - 'alias' => 'sometimes|nullable|string|max:255', + 'alias' => 'sometimes|nullable|string|max:191', 'ports' => 'required|array', 'ports.*' => 'string', ]; diff --git a/app/Http/Requests/Api/Client/Servers/Backups/StoreBackupRequest.php b/app/Http/Requests/Api/Client/Servers/Backups/StoreBackupRequest.php index e6dd2ad43..4ca892155 100644 --- a/app/Http/Requests/Api/Client/Servers/Backups/StoreBackupRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Backups/StoreBackupRequest.php @@ -21,7 +21,7 @@ class StoreBackupRequest extends ClientApiRequest public function rules(): array { return [ - 'name' => 'nullable|string|max:255', + 'name' => 'nullable|string|max:191', 'ignored' => 'nullable|string', ]; } diff --git a/app/Http/Requests/Api/Client/Servers/Subusers/StoreSubuserRequest.php b/app/Http/Requests/Api/Client/Servers/Subusers/StoreSubuserRequest.php index effc3bf3a..0848770f9 100644 --- a/app/Http/Requests/Api/Client/Servers/Subusers/StoreSubuserRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Subusers/StoreSubuserRequest.php @@ -20,7 +20,7 @@ class StoreSubuserRequest extends SubuserRequest public function rules(): array { return [ - 'email' => 'required|email', + 'email' => 'required|email|between:1,191', 'permissions' => 'required|array', 'permissions.*' => 'string', ]; diff --git a/app/Models/DatabaseHost.php b/app/Models/DatabaseHost.php index 016702141..750ca0de9 100644 --- a/app/Models/DatabaseHost.php +++ b/app/Models/DatabaseHost.php @@ -50,7 +50,7 @@ class DatabaseHost extends Model * @var array */ public static $validationRules = [ - 'name' => 'required|string|max:255', + 'name' => 'required|string|max:191', 'host' => 'required|string', 'port' => 'required|numeric|between:1,65535', 'username' => 'required|string|max:32', diff --git a/app/Models/Egg.php b/app/Models/Egg.php index 143fe95a8..4aa33beff 100644 --- a/app/Models/Egg.php +++ b/app/Models/Egg.php @@ -93,13 +93,13 @@ class Egg extends Model public static $validationRules = [ 'nest_id' => 'required|bail|numeric|exists:nests,id', 'uuid' => 'required|string|size:36', - 'name' => 'required|string|max:255', + 'name' => 'required|string|max:191', 'description' => 'string|nullable', 'author' => 'required|string|email', - 'docker_image' => 'required|string|max:255', + 'docker_image' => 'required|string|max:191', 'startup' => 'required|nullable|string', 'config_from' => 'sometimes|bail|nullable|numeric|exists:eggs,id', - 'config_stop' => 'required_without:config_from|nullable|string|max:255', + 'config_stop' => 'required_without:config_from|nullable|string|max:191', 'config_startup' => 'required_without:config_from|nullable|json', 'config_logs' => 'required_without:config_from|nullable|json', 'config_files' => 'required_without:config_from|nullable|json', diff --git a/app/Models/EggVariable.php b/app/Models/EggVariable.php index c6cc45b56..2adc35134 100644 --- a/app/Models/EggVariable.php +++ b/app/Models/EggVariable.php @@ -73,9 +73,9 @@ class EggVariable extends Model */ public static $validationRules = [ 'egg_id' => 'exists:eggs,id', - 'name' => 'required|string|between:1,255', + 'name' => 'required|string|between:1,191', 'description' => 'string', - 'env_variable' => 'required|regex:/^[\w]{1,255}$/|notIn:' . self::RESERVED_ENV_NAMES, + 'env_variable' => 'required|regex:/^[\w]{1,191}$/|notIn:' . self::RESERVED_ENV_NAMES, 'default_value' => 'string', 'user_viewable' => 'boolean', 'user_editable' => 'boolean', diff --git a/app/Models/Location.php b/app/Models/Location.php index 17ba7e24a..74fed1812 100644 --- a/app/Models/Location.php +++ b/app/Models/Location.php @@ -41,7 +41,7 @@ class Location extends Model */ public static $validationRules = [ 'short' => 'required|string|between:1,60|unique:locations,short', - 'long' => 'string|nullable|between:1,255', + 'long' => 'string|nullable|between:1,191', ]; /** diff --git a/app/Models/Mount.php b/app/Models/Mount.php index 023243f93..b69c0c78d 100644 --- a/app/Models/Mount.php +++ b/app/Models/Mount.php @@ -56,7 +56,7 @@ class Mount extends Model */ public static $validationRules = [ 'name' => 'required|string|min:2|max:64|unique:mounts,name', - 'description' => 'nullable|string|max:255', + 'description' => 'nullable|string|max:191', 'source' => 'required|string', 'target' => 'required|string', 'read_only' => 'sometimes|boolean', diff --git a/app/Models/Nest.php b/app/Models/Nest.php index 5def1c833..770c5baf7 100644 --- a/app/Models/Nest.php +++ b/app/Models/Nest.php @@ -44,7 +44,7 @@ class Nest extends Model */ public static $validationRules = [ 'author' => 'required|string|email', - 'name' => 'required|string|max:255', + 'name' => 'required|string|max:191', 'description' => 'nullable|string', ]; diff --git a/app/Models/Schedule.php b/app/Models/Schedule.php index bc214f27d..d737edd2c 100644 --- a/app/Models/Schedule.php +++ b/app/Models/Schedule.php @@ -103,7 +103,7 @@ class Schedule extends Model */ public static $validationRules = [ 'server_id' => 'required|exists:servers,id', - 'name' => 'required|string|max:255', + 'name' => 'required|string|max:191', 'cron_day_of_week' => 'required|string', 'cron_day_of_month' => 'required|string', 'cron_hour' => 'required|string', diff --git a/app/Models/Server.php b/app/Models/Server.php index f6de3516c..13fcb931a 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -103,7 +103,7 @@ class Server extends Model public static $validationRules = [ 'external_id' => 'sometimes|nullable|string|between:1,191|unique:servers', 'owner_id' => 'required|integer|exists:users,id', - 'name' => 'required|string|min:1|max:255', + 'name' => 'required|string|min:1|max:191', 'node_id' => 'required|exists:nodes,id', 'description' => 'string', 'memory' => 'required|numeric|min:0', @@ -118,7 +118,7 @@ class Server extends Model 'egg_id' => 'required|exists:eggs,id', 'startup' => 'required|string', 'skip_scripts' => 'sometimes|boolean', - 'image' => 'required|string|max:255', + 'image' => 'required|string|max:191', 'installed' => 'in:0,1,2', 'database_limit' => 'present|nullable|integer|min:0', 'allocation_limit' => 'sometimes|nullable|integer|min:0', diff --git a/app/Models/Setting.php b/app/Models/Setting.php index 1a91a578e..458248628 100644 --- a/app/Models/Setting.php +++ b/app/Models/Setting.php @@ -25,7 +25,7 @@ class Setting extends Model * @var array */ public static $validationRules = [ - 'key' => 'required|string|between:1,255', + 'key' => 'required|string|between:1,191', 'value' => 'string', ]; } diff --git a/app/Models/User.php b/app/Models/User.php index d2761bd72..24ef981f4 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -137,11 +137,11 @@ class User extends Model implements */ public static $validationRules = [ 'uuid' => 'required|string|size:36|unique:users,uuid', - 'email' => 'required|email|unique:users,email', - 'external_id' => 'sometimes|nullable|string|max:255|unique:users,external_id', - 'username' => 'required|between:1,255|unique:users,username', - 'name_first' => 'required|string|between:1,255', - 'name_last' => 'required|string|between:1,255', + 'email' => 'required|email|between:1,191|unique:users,email', + 'external_id' => 'sometimes|nullable|string|max:191|unique:users,external_id', + 'username' => 'required|between:1,191|unique:users,username', + 'name_first' => 'required|string|between:1,191', + 'name_last' => 'required|string|between:1,191', 'password' => 'sometimes|nullable|string', 'root_admin' => 'boolean', 'language' => 'string', diff --git a/resources/scripts/components/server/backups/CreateBackupButton.tsx b/resources/scripts/components/server/backups/CreateBackupButton.tsx index 9218b68ec..f6af35ca0 100644 --- a/resources/scripts/components/server/backups/CreateBackupButton.tsx +++ b/resources/scripts/components/server/backups/CreateBackupButton.tsx @@ -87,7 +87,7 @@ export default () => { onSubmit={submit} initialValues={{ name: '', ignored: '' }} validationSchema={object().shape({ - name: string().max(255), + name: string().max(191), ignored: string(), })} > diff --git a/resources/scripts/components/server/users/EditSubuserModal.tsx b/resources/scripts/components/server/users/EditSubuserModal.tsx index 482a6b86b..d81d15f08 100644 --- a/resources/scripts/components/server/users/EditSubuserModal.tsx +++ b/resources/scripts/components/server/users/EditSubuserModal.tsx @@ -250,7 +250,10 @@ export default ({ subuser, ...props }: Props) => { permissions: subuser?.permissions || [], } as Values} validationSchema={object().shape({ - email: string().email('A valid email address must be provided.').required('A valid email address must be provided.'), + email: string() + .max(191, 'Email addresses must not exceed 191 characters.') + .email('A valid email address must be provided.') + .required('A valid email address must be provided.'), permissions: array().of(string()), })} > diff --git a/resources/views/admin/locations/index.blade.php b/resources/views/admin/locations/index.blade.php index 53bd3cd71..3d5128db2 100644 --- a/resources/views/admin/locations/index.blade.php +++ b/resources/views/admin/locations/index.blade.php @@ -70,7 +70,7 @@
-

A longer description of this location. Must be less than 255 characters.

+

A longer description of this location. Must be less than 191 characters.

diff --git a/resources/views/admin/mounts/index.blade.php b/resources/views/admin/mounts/index.blade.php index da554cd04..309dc3c17 100644 --- a/resources/views/admin/mounts/index.blade.php +++ b/resources/views/admin/mounts/index.blade.php @@ -84,7 +84,7 @@
-

A longer description for this mount, must be less than 255 characters.

+

A longer description for this mount, must be less than 191 characters.

From 47697e07cfeee569bea15d57ab1eaac07a89cfc4 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 26 Sep 2020 17:25:14 -0700 Subject: [PATCH 04/71] Add test coverage for creating a subuser --- .../Subuser/CreateServerSubuserTest.php | 166 ++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 tests/Integration/Api/Client/Server/Subuser/CreateServerSubuserTest.php diff --git a/tests/Integration/Api/Client/Server/Subuser/CreateServerSubuserTest.php b/tests/Integration/Api/Client/Server/Subuser/CreateServerSubuserTest.php new file mode 100644 index 000000000..53a299e02 --- /dev/null +++ b/tests/Integration/Api/Client/Server/Subuser/CreateServerSubuserTest.php @@ -0,0 +1,166 @@ +generateTestAccount($permissions); + + $response = $this->actingAs($user)->postJson($this->link($server) . "/users", [ + 'email' => $email = $this->faker->email, + 'permissions' => [ + Permission::ACTION_USER_CREATE, + ], + ]); + + $response->assertOk(); + + /** @var \Pterodactyl\Models\User $subuser */ + $subuser = User::query()->where('email', $email)->firstOrFail(); + + $response->assertJsonPath('object', Subuser::RESOURCE_NAME); + $response->assertJsonPath('attributes.uuid', $subuser->uuid); + $response->assertJsonPath('attributes.permissions', [ + Permission::ACTION_USER_CREATE, + Permission::ACTION_WEBSOCKET_CONNECT, + ]); + + $expected = $response->json('attributes'); + unset($expected['permissions']); + + $this->assertJsonTransformedWith($expected, $subuser); + } + + /** + * Tests that an error is returned if a subuser attempts to create a new subuser and assign + * permissions that their account does not also possess. + */ + public function testErrorIsReturnedIfAssigningPermissionsNotAssignedToSelf() + { + [$user, $server] = $this->generateTestAccount([ + Permission::ACTION_USER_CREATE, + Permission::ACTION_USER_READ, + Permission::ACTION_CONTROL_CONSOLE, + ]); + + $response = $this->actingAs($user)->postJson($this->link($server) . "/users", [ + 'email' => $email = $this->faker->email, + 'permissions' => [ + Permission::ACTION_USER_CREATE, + Permission::ACTION_USER_UPDATE, // This permission is not assigned to the subuser. + ], + ]); + + $response->assertForbidden(); + $response->assertJsonPath('errors.0.code', 'HttpForbiddenException'); + $response->assertJsonPath('errors.0.detail', 'Cannot assign permissions to a subuser that your account does not actively possess.'); + } + + /** + * Throws some bad data at the API and ensures that a subuser cannot be created. + */ + public function testSubuserWithExcessivelyLongEmailCannotBeCreated() + { + [$user, $server] = $this->generateTestAccount(); + + $email = str_repeat(Str::random(20), 9) . '1@gmail.com'; // 191 is the hard limit for the column in MySQL. + + $response = $this->actingAs($user)->postJson($this->link($server) . "/users", [ + 'email' => $email, + 'permissions' => [ + Permission::ACTION_USER_CREATE, + ], + ]); + + $response->assertOk(); + + $response = $this->actingAs($user)->postJson($this->link($server) . "/users", [ + 'email' => $email . '.au', + 'permissions' => [ + Permission::ACTION_USER_CREATE, + ], + ]); + + $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY); + $response->assertJsonPath('errors.0.detail', 'The email must be between 1 and 191 characters.'); + $response->assertJsonPath('errors.0.meta.source_field', 'email'); + } + + /** + * Test that creating a subuser when there is already an account with that email runs + * as expected and does not create a new account. + */ + public function testCreatingSubuserWithSameEmailAsExistingUserWorks() + { + [$user, $server] = $this->generateTestAccount(); + + /** @var \Pterodactyl\Models\User $existing */ + $existing = factory(User::class)->create(['email' => $this->faker->email]); + + $response = $this->actingAs($user)->postJson($this->link($server) . "/users", [ + 'email' => $existing->email, + 'permissions' => [ + Permission::ACTION_USER_CREATE, + ], + ]); + + $response->assertOk(); + $response->assertJsonPath('object', Subuser::RESOURCE_NAME); + $response->assertJsonPath('attributes.uuid', $existing->uuid); + } + + /** + * Test that an error is returned if the account associated with an email address is already + * associated with the server instance. + */ + public function testAddingSubuserThatAlreadyIsAssignedReturnsError() + { + [$user, $server] = $this->generateTestAccount(); + + $response = $this->actingAs($user)->postJson($this->link($server) . "/users", [ + 'email' => $email = $this->faker->email, + 'permissions' => [ + Permission::ACTION_USER_CREATE, + ], + ]); + + $response->assertOk(); + + $response = $this->actingAs($user)->postJson($this->link($server) . "/users", [ + 'email' => $email, + 'permissions' => [ + Permission::ACTION_USER_CREATE, + ], + ]); + + $response->assertStatus(Response::HTTP_BAD_REQUEST); + $response->assertJsonPath('errors.0.code', 'ServerSubuserExistsException'); + $response->assertJsonPath('errors.0.detail', 'A user with that email address is already assigned as a subuser for this server.'); + } + + /** + * @return array + */ + public function permissionsDataProvider(): array + { + return [[[]], [[Permission::ACTION_USER_CREATE]]]; + } +} From 3e899ffc5177fcf5a941c6582550fedd00302f19 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 27 Sep 2020 09:22:09 -0700 Subject: [PATCH 05/71] Handle JWT expirations a little better --- .../components/server/WebsocketHandler.tsx | 53 ++++++++++++++----- 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/resources/scripts/components/server/WebsocketHandler.tsx b/resources/scripts/components/server/WebsocketHandler.tsx index fe43e56d5..f53466e0a 100644 --- a/resources/scripts/components/server/WebsocketHandler.tsx +++ b/resources/scripts/components/server/WebsocketHandler.tsx @@ -8,20 +8,27 @@ import Spinner from '@/components/elements/Spinner'; import tw from 'twin.macro'; export default () => { - const server = ServerContext.useStoreState(state => state.server.data); - const [ error, setError ] = useState(false); + let updatingToken = false; + const [ error, setError ] = useState<'connecting' | string>(''); const { connected, instance } = ServerContext.useStoreState(state => state.socket); + const uuid = ServerContext.useStoreState(state => state.server.data?.uuid); const setServerStatus = ServerContext.useStoreActions(actions => actions.status.setServerStatus); const { setInstance, setConnectionState } = ServerContext.useStoreActions(actions => actions.socket); const updateToken = (uuid: string, socket: Websocket) => { + if (updatingToken) return; + + updatingToken = true; getWebsocketToken(uuid) .then(data => socket.setToken(data.token, true)) - .catch(error => console.error(error)); + .catch(error => console.error(error)) + .then(() => { + updatingToken = false; + }); }; useEffect(() => { - connected && setError(false); + connected && setError(''); }, [ connected ]); useEffect(() => { @@ -33,7 +40,7 @@ export default () => { useEffect(() => { // If there is already an instance or there is no server, just exit out of this process // since we don't need to make a new connection. - if (instance || !server) { + if (instance || !uuid) { return; } @@ -42,7 +49,7 @@ export default () => { socket.on('auth success', () => setConnectionState(true)); socket.on('SOCKET_CLOSE', () => setConnectionState(false)); socket.on('SOCKET_ERROR', () => { - setError(true); + setError('connecting'); setConnectionState(false); }); socket.on('status', (status) => setServerStatus(status)); @@ -51,10 +58,20 @@ export default () => { console.warn('Got error message from daemon socket:', message); }); - socket.on('token expiring', () => updateToken(server.uuid, socket)); - socket.on('token expired', () => updateToken(server.uuid, socket)); + socket.on('token expiring', () => updateToken(uuid, socket)); + socket.on('token expired', () => updateToken(uuid, socket)); + socket.on('jwt error', (error: string) => { + setConnectionState(false); + console.warn('JWT validation error from wings:', error); - getWebsocketToken(server.uuid) + if (error === 'jwt: exp claim is invalid') { + updateToken(uuid, socket); + } else { + setError('There was an error validating the credentials provided for the websocket. Please refresh the page.'); + } + }); + + getWebsocketToken(uuid) .then(data => { // Connect and then set the authentication token. socket.setToken(data.token).connect(data.socket); @@ -63,17 +80,25 @@ export default () => { setInstance(socket); }) .catch(error => console.error(error)); - }, [ server ]); + }, [ uuid ]); return ( error ?
- -

- We're having some trouble connecting to your server, please wait... -

+ {error === 'connecting' ? + <> + +

+ We're having some trouble connecting to your server, please wait... +

+ + : +

+ {error} +

+ }
From da0b5277152a46d4ba93502de9e8e52642d92540 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 27 Sep 2020 09:30:05 -0700 Subject: [PATCH 06/71] Do not re-render rows excessively --- .../scripts/components/server/files/FileObjectRow.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/resources/scripts/components/server/files/FileObjectRow.tsx b/resources/scripts/components/server/files/FileObjectRow.tsx index 0e05820bd..53005653b 100644 --- a/resources/scripts/components/server/files/FileObjectRow.tsx +++ b/resources/scripts/components/server/files/FileObjectRow.tsx @@ -92,4 +92,11 @@ const FileObjectRow = ({ file }: { file: FileObject }) => ( ); -export default memo(FileObjectRow, isEqual); +export default memo(FileObjectRow, (prevProps, nextProps) => { + /* eslint-disable @typescript-eslint/no-unused-vars */ + const { isArchiveType, isEditable, ...prevFile } = prevProps.file; + const { isArchiveType: nextIsArchiveType, isEditable: nextIsEditable, ...nextFile } = nextProps.file; + /* eslint-enable @typescript-eslint/no-unused-vars */ + + return isEqual(prevFile, nextFile); +}); From ff50940fa67dd193d8a671dc7344d2c3da7307d5 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 27 Sep 2020 09:30:24 -0700 Subject: [PATCH 07/71] Fix keyboard save shortcut when saving a new file; closes #2427 --- .../scripts/components/server/files/FileEditContainer.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/resources/scripts/components/server/files/FileEditContainer.tsx b/resources/scripts/components/server/files/FileEditContainer.tsx index 5d8102728..87eda2eb5 100644 --- a/resources/scripts/components/server/files/FileEditContainer.tsx +++ b/resources/scripts/components/server/files/FileEditContainer.tsx @@ -116,7 +116,13 @@ export default () => { fetchContent={value => { fetchFileContent = value; }} - onContentSaved={save} + onContentSaved={() => { + if (action !== 'edit') { + setModalVisible(true); + } else { + save(); + } + }} />
From 1db7e4db665ebac61510dbf90c93abf145e264dc Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 27 Sep 2020 09:45:37 -0700 Subject: [PATCH 08/71] Request stats from wings when loading the console; closes pterodactyl/panel#2414 --- resources/scripts/components/server/Console.tsx | 1 + resources/scripts/components/server/ServerConsole.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/resources/scripts/components/server/Console.tsx b/resources/scripts/components/server/Console.tsx index 49fb59cf0..3840934b8 100644 --- a/resources/scripts/components/server/Console.tsx +++ b/resources/scripts/components/server/Console.tsx @@ -52,6 +52,7 @@ const TerminalDiv = styled.div` export default () => { const TERMINAL_PRELUDE = '\u001b[1m\u001b[33mcontainer@pterodactyl~ \u001b[0m'; const [ terminalElement, setTerminalElement ] = useState(null); + const useRef = useCallback(node => setTerminalElement(node), []); const terminal = useMemo(() => new Terminal({ ...terminalProps }), []); const { connected, instance } = ServerContext.useStoreState(state => state.socket); diff --git a/resources/scripts/components/server/ServerConsole.tsx b/resources/scripts/components/server/ServerConsole.tsx index d3068c8f1..05470078a 100644 --- a/resources/scripts/components/server/ServerConsole.tsx +++ b/resources/scripts/components/server/ServerConsole.tsx @@ -53,6 +53,7 @@ export default () => { } instance.addListener('stats', statsListener); + instance.send('send stats'); return () => { instance.removeListener('stats', statsListener); From f31a6d3967b89b7b37ef41d677efd0add73de606 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 27 Sep 2020 10:39:18 -0700 Subject: [PATCH 09/71] Fix parameter bindings for client API routes; closes pterodactyl/panel#2359 --- .../Client/SubstituteClientApiBindings.php | 4 +- .../Subuser/CreateServerSubuserTest.php | 2 +- .../Server/Subuser/DeleteSubuserTest.php | 59 +++++++++++++++++++ 3 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 tests/Integration/Api/Client/Server/Subuser/DeleteSubuserTest.php diff --git a/app/Http/Middleware/Api/Client/SubstituteClientApiBindings.php b/app/Http/Middleware/Api/Client/SubstituteClientApiBindings.php index 77879c97f..7ab597b63 100644 --- a/app/Http/Middleware/Api/Client/SubstituteClientApiBindings.php +++ b/app/Http/Middleware/Api/Client/SubstituteClientApiBindings.php @@ -49,11 +49,11 @@ class SubstituteClientApiBindings extends ApiSubstituteBindings return Database::query()->where('id', $id)->firstOrFail(); }); - $this->router->model('backup', Backup::class, function ($value) { + $this->router->bind('backup', function ($value) { return Backup::query()->where('uuid', $value)->firstOrFail(); }); - $this->router->model('user', User::class, function ($value) { + $this->router->bind('user', function ($value) { return User::query()->where('uuid', $value)->firstOrFail(); }); diff --git a/tests/Integration/Api/Client/Server/Subuser/CreateServerSubuserTest.php b/tests/Integration/Api/Client/Server/Subuser/CreateServerSubuserTest.php index 53a299e02..3a1f8bc6b 100644 --- a/tests/Integration/Api/Client/Server/Subuser/CreateServerSubuserTest.php +++ b/tests/Integration/Api/Client/Server/Subuser/CreateServerSubuserTest.php @@ -1,6 +1,6 @@ generateTestAccount(); + + /** @var \Pterodactyl\Models\User $differentUser */ + $differentUser = factory(User::class)->create(); + + // Generate a UUID that lines up with a user in the database if it were to be cast to an int. + $uuid = $differentUser->id . str_repeat('a', strlen((string)$differentUser->id)) . substr(Uuid::uuid4()->toString(), 8); + + /** @var \Pterodactyl\Models\User $subuser */ + $subuser = factory(User::class)->create(['uuid' => $uuid]); + + Subuser::query()->forceCreate([ + 'user_id' => $subuser->id, + 'server_id' => $server->id, + 'permissions' => [ Permission::ACTION_WEBSOCKET_CONNECT ], + ]); + + $this->actingAs($user)->deleteJson($this->link($server) . "/users/{$subuser->uuid}")->assertNoContent(); + + // Try the same test, but this time with a UUID that if cast to an int (shouldn't) line up with + // anything in the database. + $uuid = '18180000' . substr(Uuid::uuid4()->toString(), 8); + /** @var \Pterodactyl\Models\User $subuser */ + $subuser = factory(User::class)->create(['uuid' => $uuid]); + + Subuser::query()->forceCreate([ + 'user_id' => $subuser->id, + 'server_id' => $server->id, + 'permissions' => [ Permission::ACTION_WEBSOCKET_CONNECT ], + ]); + + $this->actingAs($user)->deleteJson($this->link($server) . "/users/{$subuser->uuid}")->assertNoContent(); + } +} From 1616cf1318854e9f8d9bdeb0862c5fdea589a7d1 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 3 Oct 2020 11:18:14 -0700 Subject: [PATCH 10/71] Always show errors from HTTP exceptions correctly to the user; closes #2439 --- app/Exceptions/Handler.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index f84c7077b..7cd007792 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -17,6 +17,7 @@ use Illuminate\Database\Eloquent\ModelNotFoundException; use Symfony\Component\HttpKernel\Exception\HttpException; use Pterodactyl\Exceptions\Repository\RecordNotFoundException; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; class Handler extends ExceptionHandler { @@ -217,7 +218,9 @@ class Handler extends ExceptionHandler 'status' => method_exists($exception, 'getStatusCode') ? strval($exception->getStatusCode()) : ($exception instanceof ValidationException ? '422' : '500'), - 'detail' => 'An error was encountered while processing this request.', + 'detail' => $exception instanceof HttpExceptionInterface + ? $exception->getMessage() + : 'An unexpected error was ecnountered while processing this request, please try again.', ]; if ($exception instanceof ModelNotFoundException || $exception->getPrevious() instanceof ModelNotFoundException) { From 6893f9bfcdc858a17f2e7144967ba84917a7ff9f Mon Sep 17 00:00:00 2001 From: Oreo Oreoniv <28255085+zKoz210@users.noreply.github.com> Date: Sat, 3 Oct 2020 21:21:09 +0300 Subject: [PATCH 11/71] Fixes for mobile responsiveness on the console page (#2411) --- .../components/server/ServerConsole.tsx | 4 +- .../scripts/components/server/StatGraphs.tsx | 40 ++++++++++--------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/resources/scripts/components/server/ServerConsole.tsx b/resources/scripts/components/server/ServerConsole.tsx index 05470078a..edf10d130 100644 --- a/resources/scripts/components/server/ServerConsole.tsx +++ b/resources/scripts/components/server/ServerConsole.tsx @@ -65,7 +65,7 @@ export default () => { return ( -
+

{

}
-
+
diff --git a/resources/scripts/components/server/StatGraphs.tsx b/resources/scripts/components/server/StatGraphs.tsx index 399b96724..725373b30 100644 --- a/resources/scripts/components/server/StatGraphs.tsx +++ b/resources/scripts/components/server/StatGraphs.tsx @@ -142,24 +142,28 @@ export default () => { return (
- - {status !== 'offline' ? - - : -

- Server is offline. -

- } -
- - {status !== 'offline' ? - - : -

- Server is offline. -

- } -
+
+ + {status !== 'offline' ? + + : +

+ Server is offline. +

+ } +
+
+
+ + {status !== 'offline' ? + + : +

+ Server is offline. +

+ } +
+
); }; From 10eeae09273dc20f24f9929567e04a4d0f6e0e34 Mon Sep 17 00:00:00 2001 From: Oreo Oreoniv <28255085+zKoz210@users.noreply.github.com> Date: Sat, 3 Oct 2020 21:22:37 +0300 Subject: [PATCH 12/71] Fix text overflows through out the front end (#2406) --- resources/scripts/components/elements/Modal.tsx | 2 ++ resources/scripts/components/server/backups/BackupRow.tsx | 4 +++- resources/scripts/components/server/files/FileObjectRow.tsx | 2 +- .../scripts/components/server/files/NewDirectoryButton.tsx | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/resources/scripts/components/elements/Modal.tsx b/resources/scripts/components/elements/Modal.tsx index 10401e9ca..2980fecd9 100644 --- a/resources/scripts/components/elements/Modal.tsx +++ b/resources/scripts/components/elements/Modal.tsx @@ -35,6 +35,8 @@ const ModalContainer = styled.div<{ alignTop?: boolean }>` margin-top: 20%; ${breakpoint('md')`margin-top: 10%`}; `}; + + margin-bottom: 5%; & > .close-icon { ${tw`absolute right-0 p-2 text-white cursor-pointer opacity-50 transition-all duration-150 ease-linear hover:opacity-100`}; diff --git a/resources/scripts/components/server/backups/BackupRow.tsx b/resources/scripts/components/server/backups/BackupRow.tsx index 0a45c7ca9..fbbb849f9 100644 --- a/resources/scripts/components/server/backups/BackupRow.tsx +++ b/resources/scripts/components/server/backups/BackupRow.tsx @@ -55,7 +55,9 @@ export default ({ backup, className }: Props) => { Failed } - {backup.name} +
+ {backup.name} +
{(backup.completedAt && backup.isSuccessful) && } diff --git a/resources/scripts/components/server/files/FileObjectRow.tsx b/resources/scripts/components/server/files/FileObjectRow.tsx index 53005653b..43416eb4c 100644 --- a/resources/scripts/components/server/files/FileObjectRow.tsx +++ b/resources/scripts/components/server/files/FileObjectRow.tsx @@ -69,7 +69,7 @@ const FileObjectRow = ({ file }: { file: FileObject }) => ( }
-
+
{file.name}
{file.isFile && diff --git a/resources/scripts/components/server/files/NewDirectoryButton.tsx b/resources/scripts/components/server/files/NewDirectoryButton.tsx index e1f92e0e7..2be673db9 100644 --- a/resources/scripts/components/server/files/NewDirectoryButton.tsx +++ b/resources/scripts/components/server/files/NewDirectoryButton.tsx @@ -88,7 +88,7 @@ export default ({ className }: WithClassname) => { name={'directoryName'} label={'Directory Name'} /> -

+

This directory will be created as  /home/container/ From 3473e1dfbfe6ff2c05aa9c338a74014c55d55f91 Mon Sep 17 00:00:00 2001 From: Charles Morgan Date: Sat, 3 Oct 2020 14:23:16 -0400 Subject: [PATCH 13/71] Remove console logging (#2415) Remove console logging, as its not an error being logged i see no reason for this to be printed when the startup page is viewed. --- resources/scripts/api/swr/getServerStartup.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/resources/scripts/api/swr/getServerStartup.ts b/resources/scripts/api/swr/getServerStartup.ts index fff0263f9..892f78fdd 100644 --- a/resources/scripts/api/swr/getServerStartup.ts +++ b/resources/scripts/api/swr/getServerStartup.ts @@ -9,7 +9,6 @@ interface Response { } export default (uuid: string, initialData?: Response) => useSWR([ uuid, '/startup' ], async (): Promise => { - console.log('firing getServerStartup'); const { data } = await http.get(`/api/client/servers/${uuid}/startup`); const variables = ((data as FractalResponseList).data || []).map(rawDataToServerEggVariable); From 62856556b901def3dcc651e6ee9f114191443c9b Mon Sep 17 00:00:00 2001 From: Stepan Fedotov Date: Sat, 3 Oct 2020 19:55:35 +0300 Subject: [PATCH 14/71] Apply security fixes from #2441 to 1.0 --- CHANGELOG.md | 13 ++++++- .../themes/pterodactyl/js/admin/new-server.js | 37 +++++++++++-------- .../admin/servers/view/details.blade.php | 18 ++++++--- 3 files changed, 44 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efd2cef5b..8958e82be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,15 @@ This file is a running track of new features and fixes to each version of the pa This project follows [Semantic Versioning](http://semver.org) guidelines. +## v0.7.19 (Derelict Dermodactylus) +### Fixed +* **[Security]** Fixes XSS in the admin area's server owner selection. + +## v0.7.18 (Derelict Dermodactylus) +### Fixed +* **[Security]** Re-addressed missed endpoint that would not properly limit a user account to 5 API keys. +* **[Security]** Addresses a Client API vulnerability that would allow a user to list all servers on the system ([`GHSA-6888-7f3w-92jx`](https://github.com/pterodactyl/panel/security/advisories/GHSA-6888-7f3w-92jx)) + ## v0.7.17 (Derelict Dermodactylus) ### Fixed * Limited accounts to 5 API keys at a time. @@ -301,7 +310,7 @@ the response from the server `GET` endpoint. * Nest and Egg listings now show the associated ID in order to make API requests easier. * Added star indicators to user listing in Admin CP to indicate users who are set as a root admin. * Creating a new node will now requires a SSL connection if the Panel is configured to use SSL as well. -* Connector error messages due to permissions are now rendered correctly in the UI rather than causing a silent failure. +* Socketio error messages due to permissions are now rendered correctly in the UI rather than causing a silent failure. * File manager now supports mass deletion option for files and folders. * Support for CS:GO as a default service option selection. * Support for GMOD as a default service option selection. @@ -431,7 +440,7 @@ the response from the server `GET` endpoint. * Changed 2FA login process to be more secure. Previously authentication checking happened on the 2FA post page, now it happens prior and is passed along to the 2FA page to avoid storing any credentials. ### Added -* Connector error messages due to permissions are now rendered correctly in the UI rather than causing a silent failure. +* Socketio error messages due to permissions are now rendered correctly in the UI rather than causing a silent failure. ## v0.7.0-beta.1 (Derelict Dermodactylus) ### Added diff --git a/public/themes/pterodactyl/js/admin/new-server.js b/public/themes/pterodactyl/js/admin/new-server.js index 7416451da..cda0d5cf3 100644 --- a/public/themes/pterodactyl/js/admin/new-server.js +++ b/public/themes/pterodactyl/js/admin/new-server.js @@ -153,6 +153,12 @@ function updateAdditionalAllocations() { } function initUserIdSelect(data) { + function escapeHtml(str) { + var div = document.createElement('div'); + div.appendChild(document.createTextNode(str)); + return div.innerHTML; + } + $('#pUserId').select2({ ajax: { url: '/admin/users/accounts.json', @@ -176,28 +182,27 @@ function initUserIdSelect(data) { data: data, escapeMarkup: function (markup) { return markup; }, minimumInputLength: 2, - templateResult: function (data) { - if (data.loading) return data.text; + if (data.loading) return escapeHtml(data.text); return '

\ - User Image \ - \ - ' + data.name_first + ' ' + data.name_last +' \ - \ - ' + data.email + ' - ' + data.username + ' \ -
'; + User Image \ + \ + ' + escapeHtml(data.name_first) + ' ' + escapeHtml(data.name_last) +' \ + \ + ' + escapeHtml(data.email) + ' - ' + escapeHtml(data.username) + ' \ +
'; }, - templateSelection: function (data) { return '
\ - \ - User Image \ - \ - \ - ' + data.name_first + ' ' + data.name_last + ' (' + data.email + ') \ - \ -
'; + \ + User Image \ + \ + \ + ' + escapeHtml(data.name_first) + ' ' + escapeHtml(data.name_last) + ' (' + escapeHtml(data.email) + ') \ + \ +
'; } + }); } diff --git a/resources/views/admin/servers/view/details.blade.php b/resources/views/admin/servers/view/details.blade.php index 38c3a487d..a3d6be628 100644 --- a/resources/views/admin/servers/view/details.blade.php +++ b/resources/views/admin/servers/view/details.blade.php @@ -66,6 +66,12 @@ @section('footer-scripts') @parent - {!! Theme::js('js/admin/new-server.js?v=20200913') !!} + {!! Theme::js('js/admin/new-server.js?v=20201003') !!}