From c86690a69595c207f2b368eab2d6798fa8994b6e Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Thu, 31 May 2018 21:45:49 -0700 Subject: [PATCH 01/14] Get base code for dusk tests --- .env.dusk | 25 ++++++ composer.json | 2 + composer.lock | 123 ++++++++++++++++++++++++++- phpunit.xml | 3 + tests/Browser/BrowserTestCase.php | 44 ++++++++++ tests/Browser/PterodactylBrowser.php | 9 ++ tests/Browser/console/.gitignore | 2 + tests/Browser/screenshots/.gitignore | 2 + 8 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 .env.dusk create mode 100644 tests/Browser/BrowserTestCase.php create mode 100644 tests/Browser/PterodactylBrowser.php create mode 100644 tests/Browser/console/.gitignore create mode 100644 tests/Browser/screenshots/.gitignore diff --git a/.env.dusk b/.env.dusk new file mode 100644 index 000000000..1934ad3d5 --- /dev/null +++ b/.env.dusk @@ -0,0 +1,25 @@ +APP_ENV=local +APP_DEBUG=false +APP_KEY=NDWgIKKi9ovNK1PXZpzfNVSBdfCXGb5i +APP_JWT_KEY=test1234 +APP_TIMEZONE=America/Los_Angeles +APP_URL=http://192.168.1.249 + +CACHE_DRIVER=file +SESSION_DRIVER=file + +HASHIDS_SALT=IqRr0g82tCTeuyxGs8RV +HASHIDS_LENGTH=8 + +MAIL_DRIVER=log +MAIL_FROM=support@pterodactyl.io +QUEUE_DRIVER=array + +APP_SERVICE_AUTHOR=testing@pterodactyl.io +MAIL_FROM_NAME="Pterodactyl Panel" +RECAPTCHA_ENABLED=false + +DB_HOST=services.pterodactyl.local +DB_DATABASE=panel_test +DB_USERNAME=panel_test +DB_PASSWORD=Test1234 diff --git a/composer.json b/composer.json index d2ac885f0..e0b6dd39d 100644 --- a/composer.json +++ b/composer.json @@ -47,6 +47,7 @@ "filp/whoops": "^2.1", "friendsofphp/php-cs-fixer": "^2.11.1", "fzaninotto/faker": "^1.6", + "laravel/dusk": "^3.0", "martinlindhe/laravel-vue-i18n-generator": "^0.1.28", "mockery/mockery": "^1.0", "nunomaduro/collision": "^2.0", @@ -67,6 +68,7 @@ }, "autoload-dev": { "psr-4": { + "Pterodactyl\\Tests\\Browser\\": "tests/Browser", "Pterodactyl\\Tests\\Integration\\": "tests/Integration", "Tests\\": "tests/" } diff --git a/composer.lock b/composer.lock index bbb170e99..b55adab6f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "9232ff40da15c9430731254edc662eb7", + "content-hash": "9055a451d415d482a2f7287e0787bbc3", "packages": [ { "name": "appstract/laravel-blade-directives", @@ -4634,6 +4634,66 @@ ], "time": "2017-07-22T11:58:36+00:00" }, + { + "name": "facebook/webdriver", + "version": "1.6.0", + "source": { + "type": "git", + "url": "https://github.com/facebook/php-webdriver.git", + "reference": "bd8c740097eb9f2fc3735250fc1912bc811a954e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/facebook/php-webdriver/zipball/bd8c740097eb9f2fc3735250fc1912bc811a954e", + "reference": "bd8c740097eb9f2fc3735250fc1912bc811a954e", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "ext-zip": "*", + "php": "^5.6 || ~7.0", + "symfony/process": "^2.8 || ^3.1 || ^4.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.0", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "php-coveralls/php-coveralls": "^2.0", + "php-mock/php-mock-phpunit": "^1.1", + "phpunit/phpunit": "^5.7", + "sebastian/environment": "^1.3.4 || ^2.0 || ^3.0", + "squizlabs/php_codesniffer": "^2.6", + "symfony/var-dumper": "^3.3 || ^4.0" + }, + "suggest": { + "ext-SimpleXML": "For Firefox profile creation" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-community": "1.5-dev" + } + }, + "autoload": { + "psr-4": { + "Facebook\\WebDriver\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "A PHP client for Selenium WebDriver", + "homepage": "https://github.com/facebook/php-webdriver", + "keywords": [ + "facebook", + "php", + "selenium", + "webdriver" + ], + "time": "2018-05-16T17:37:13+00:00" + }, { "name": "filp/whoops", "version": "2.1.14", @@ -4944,6 +5004,67 @@ ], "time": "2016-02-11T16:21:17+00:00" }, + { + "name": "laravel/dusk", + "version": "v3.0.8", + "source": { + "type": "git", + "url": "https://github.com/laravel/dusk.git", + "reference": "c6201427e63b869b0c1ee83d91c1d1958b71968e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/dusk/zipball/c6201427e63b869b0c1ee83d91c1d1958b71968e", + "reference": "c6201427e63b869b0c1ee83d91c1d1958b71968e", + "shasum": "" + }, + "require": { + "facebook/webdriver": "~1.0", + "illuminate/console": "~5.6", + "illuminate/support": "~5.6", + "nesbot/carbon": "~1.20", + "php": ">=7.1.0", + "symfony/console": "~4.0", + "symfony/process": "~4.0" + }, + "require-dev": { + "mockery/mockery": "~1.0", + "phpunit/phpunit": "~7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Dusk\\DuskServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Dusk\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel Dusk provides simple end-to-end testing and browser automation.", + "keywords": [ + "laravel", + "testing", + "webdriver" + ], + "time": "2018-04-29T19:15:23+00:00" + }, { "name": "martinlindhe/laravel-vue-i18n-generator", "version": "0.1.28", diff --git a/phpunit.xml b/phpunit.xml index 0b67ad6ea..1bf73c4c6 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -10,6 +10,9 @@ processIsolation="false" stopOnFailure="false"> + + ./tests/Browser/Processes + ./tests/Integration diff --git a/tests/Browser/BrowserTestCase.php b/tests/Browser/BrowserTestCase.php new file mode 100644 index 000000000..3e8ae90ae --- /dev/null +++ b/tests/Browser/BrowserTestCase.php @@ -0,0 +1,44 @@ +addArguments([ + '--disable-gpu', + ]); + + return RemoteWebDriver::create( + 'http://services.pterodactyl.local:4444/wd/hub', DesiredCapabilities::chrome()->setCapability( + ChromeOptions::CAPABILITY, $options + ) + ); + } + + /** + * Return an instance of the browser to be used for tests. + * + * @param \Facebook\WebDriver\Remote\RemoteWebDriver $driver + * @return \Pterodactyl\Tests\Browser\PterodactylBrowser + */ + protected function newBrowser($driver): PterodactylBrowser + { + return new PterodactylBrowser($driver); + } +} diff --git a/tests/Browser/PterodactylBrowser.php b/tests/Browser/PterodactylBrowser.php new file mode 100644 index 000000000..ba08a0707 --- /dev/null +++ b/tests/Browser/PterodactylBrowser.php @@ -0,0 +1,9 @@ + Date: Thu, 31 May 2018 22:30:05 -0700 Subject: [PATCH 02/14] :100: Lets not accidentally drop the entire database again. --- .env.dusk | 11 ++++++----- tests/Browser/BrowserTestCase.php | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/.env.dusk b/.env.dusk index 1934ad3d5..4c8e50527 100644 --- a/.env.dusk +++ b/.env.dusk @@ -3,7 +3,7 @@ APP_DEBUG=false APP_KEY=NDWgIKKi9ovNK1PXZpzfNVSBdfCXGb5i APP_JWT_KEY=test1234 APP_TIMEZONE=America/Los_Angeles -APP_URL=http://192.168.1.249 +APP_URL=http://pterodactyl.local CACHE_DRIVER=file SESSION_DRIVER=file @@ -19,7 +19,8 @@ APP_SERVICE_AUTHOR=testing@pterodactyl.io MAIL_FROM_NAME="Pterodactyl Panel" RECAPTCHA_ENABLED=false -DB_HOST=services.pterodactyl.local -DB_DATABASE=panel_test -DB_USERNAME=panel_test -DB_PASSWORD=Test1234 +DB_CONNECTION=testing +TESTING_DB_HOST=services.pterodactyl.local +TESTING_DB_DATABASE=panel_test +TESTING_DB_USERNAME=panel_test +TESTING_DB_PASSWORD=Test1234 diff --git a/tests/Browser/BrowserTestCase.php b/tests/Browser/BrowserTestCase.php index 3e8ae90ae..3e7d08c01 100644 --- a/tests/Browser/BrowserTestCase.php +++ b/tests/Browser/BrowserTestCase.php @@ -3,7 +3,9 @@ namespace Pterodactyl\Tests\Browser; use Laravel\Dusk\TestCase; +use BadMethodCallException; use Tests\CreatesApplication; +use Illuminate\Database\Eloquent\Model; use Facebook\WebDriver\Chrome\ChromeOptions; use Facebook\WebDriver\Remote\RemoteWebDriver; use Facebook\WebDriver\Remote\DesiredCapabilities; @@ -13,6 +15,23 @@ abstract class BrowserTestCase extends TestCase { use CreatesApplication, DatabaseMigrations; + /** + * Setup tests. + */ + protected function setUp() + { + // Don't accidentally run the migrations aganist the non-testing database. Ask me + // how many times I've accidentally dropped my database... + if (env('DB_CONNECTION') !== 'testing') { + throw new BadMethodCallException('Cannot call browser tests using the non-testing database connection.'); + } + + parent::setUp(); + + // Gotta unset this to continue avoiding issues with the validation. + Model::unsetEventDispatcher(); + } + /** * Create the RemoteWebDriver instance. * From f8fa62e3d6c48b4735556a86f63ce1a86a348b1d Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Thu, 31 May 2018 22:42:52 -0700 Subject: [PATCH 03/14] First round of authentication tests --- .../scripts/components/auth/LoginForm.vue | 13 +-- tests/Browser/BrowserTestCase.php | 1 + tests/Browser/Pages/BasePage.php | 16 ++++ tests/Browser/Pages/LoginPage.php | 24 +++++ .../Authentication/LoginProcessTest.php | 88 +++++++++++++++++++ 5 files changed, 136 insertions(+), 6 deletions(-) create mode 100644 tests/Browser/Pages/BasePage.php create mode 100644 tests/Browser/Pages/LoginPage.php create mode 100644 tests/Browser/Processes/Authentication/LoginProcessTest.php diff --git a/resources/assets/scripts/components/auth/LoginForm.vue b/resources/assets/scripts/components/auth/LoginForm.vue index 07236d550..067fb9290 100644 --- a/resources/assets/scripts/components/auth/LoginForm.vue +++ b/resources/assets/scripts/components/auth/LoginForm.vue @@ -5,29 +5,30 @@ >
- - +
- - +
-
- {{ $t('auth.forgot_password.label') }} diff --git a/tests/Browser/BrowserTestCase.php b/tests/Browser/BrowserTestCase.php index 3e7d08c01..dd96930a8 100644 --- a/tests/Browser/BrowserTestCase.php +++ b/tests/Browser/BrowserTestCase.php @@ -41,6 +41,7 @@ abstract class BrowserTestCase extends TestCase { $options = (new ChromeOptions)->addArguments([ '--disable-gpu', + '--disable-infobars', ]); return RemoteWebDriver::create( diff --git a/tests/Browser/Pages/BasePage.php b/tests/Browser/Pages/BasePage.php new file mode 100644 index 000000000..7d8efb513 --- /dev/null +++ b/tests/Browser/Pages/BasePage.php @@ -0,0 +1,16 @@ + '#grid-username', + '@password' => '#grid-password', + '@loginButton' => '#grid-login-button', + '@forgotPassword' => 'a[aria-label="Forgot password"]', + ]; + } +} diff --git a/tests/Browser/Processes/Authentication/LoginProcessTest.php b/tests/Browser/Processes/Authentication/LoginProcessTest.php new file mode 100644 index 000000000..1e0f8a0be --- /dev/null +++ b/tests/Browser/Processes/Authentication/LoginProcessTest.php @@ -0,0 +1,88 @@ +user = factory(User::class)->create([ + 'email' => 'test@example.com', + 'password' => Hash::make('Password123'), + ]); + } + + /** + * Test that a user can login successfully using their email address. + */ + public function testLoginUsingEmail() + { + $this->browse(function (PterodactylBrowser $browser) { + $browser->visit(new LoginPage) + ->waitFor('@username') + ->type('@username', 'test@example.com') + ->type('@password', 'Password123') + ->click('@loginButton') + ->waitForReload() + ->assertPathIs('/') + ->assertAuthenticatedAs($this->user); + }); + } + + /** + * Test that a user can login successfully using their username. + */ + public function testLoginUsingUsername() + { + $this->browse(function (PterodactylBrowser $browser) { + $browser->visit(new LoginPage) + ->waitFor('@username') + ->type('@username', $this->user->username) + ->type('@password', 'Password123') + ->click('@loginButton') + ->waitForReload() + ->assertPathIs('/') + ->assertAuthenticatedAs($this->user); + }); + } + + /** + * Test that entering the wrong password shows the expected error and then allows + * us to login without clearing the username field. + */ + public function testLoginWithErrors() + { + $this->browse(function (PterodactylBrowser $browser) { + $browser->logout() + ->visit(new LoginPage()) + ->waitFor('@username') + ->type('@username', 'test@example.com') + ->type('@password', 'invalid') + ->click('@loginButton') + ->waitFor('.alert.error') + ->assertSeeIn('.alert.error', trans('auth.failed')) + ->assertValue('@username', 'test@example.com') + ->assertValue('@password', '') + ->assertFocused('@password') + ->type('@password', 'Password123') + ->keys('@password', [WebDriverKeys::ENTER]) + ->waitForReload() + ->assertPathIs('/') + ->assertAuthenticatedAs($this->user); + }); + } +} From cf07ba574654e9f9c81a8f7ddeaa71cec9d2c2dc Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 2 Jun 2018 15:54:52 -0700 Subject: [PATCH 04/14] Let gulp build the necessary core files using artisan --- gulpfile.js | 32 +++++++++++++++++++++++++++++++- package.json | 1 + 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index a7bcf73ce..b843f890a 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -2,6 +2,7 @@ const babel = require('gulp-babel'); const concat = require('gulp-concat'); const cssmin = require('gulp-cssmin'); const del = require('del'); +const exec = require('child_process').exec; const gulp = require('gulp'); const gulpif = require('gulp-if'); const postcss = require('gulp-postcss'); @@ -74,6 +75,32 @@ function watch() { }, scripts)); } +/** + * Generate the language files to be consumed by front end. + * + * @returns {Promise} + */ +function i18n() { + return new Promise((resolve, reject) => { + exec('php artisan vue-i18n:generate', {}, (err, stdout, stderr) => { + return err ? reject(err) : resolve({ stdout, stderr }); + }) + }) +} + +/** + * Generate the routes file to be used in Vue files. + * + * @returns {Promise} + */ +function routes() { + return new Promise((resolve, reject) => { + exec('php artisan ziggy:generate resources/assets/scripts/helpers/ziggy.js', {}, (err, stdout, stderr) => { + return err ? reject(err) : resolve({ stdout, stderr }); + }); + }) +} + /** * Cleanup unused versions of hashed assets. */ @@ -82,9 +109,12 @@ function clean() { } exports.clean = clean; +exports.i18n = i18n; +exports.routes = routes; exports.styles = styles; exports.scripts = scripts; exports.watch = watch; +gulp.task('components', gulp.parallel(i18n, routes)); gulp.task('scripts', gulp.series(clean, scripts)); -gulp.task('default', gulp.series(clean, styles, scripts)); +gulp.task('default', gulp.series(clean, i18n, routes, styles, scripts)); diff --git a/package.json b/package.json index 29711e168..d200e145a 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "build:filemanager": "./node_modules/babel-cli/bin/babel.js public/themes/pterodactyl/js/frontend/files/src --source-maps --out-file public/themes/pterodactyl/js/frontend/files/filemanager.min.js", "watch": "./node_modules/gulp-cli/bin/gulp.js watch", "build": "./node_modules/gulp-cli/bin/gulp.js default", + "build:components": "./node_modules/gulp-cli/bin/gulp.js components", "build:styles": "./node_modules/gulp-cli/bin/gulp.js styles", "build:scripts": "./node_modules/gulp-cli/bin/gulp.js scripts" }, From ebb7b6de9bcd2b8df73e1d5f2d4c27782f2cb673 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 2 Jun 2018 15:54:52 -0700 Subject: [PATCH 05/14] Let gulp build the necessary core files using artisan --- gulpfile.js | 32 +++++++++++++++++++++++++++++++- package.json | 1 + 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index a7bcf73ce..b843f890a 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -2,6 +2,7 @@ const babel = require('gulp-babel'); const concat = require('gulp-concat'); const cssmin = require('gulp-cssmin'); const del = require('del'); +const exec = require('child_process').exec; const gulp = require('gulp'); const gulpif = require('gulp-if'); const postcss = require('gulp-postcss'); @@ -74,6 +75,32 @@ function watch() { }, scripts)); } +/** + * Generate the language files to be consumed by front end. + * + * @returns {Promise} + */ +function i18n() { + return new Promise((resolve, reject) => { + exec('php artisan vue-i18n:generate', {}, (err, stdout, stderr) => { + return err ? reject(err) : resolve({ stdout, stderr }); + }) + }) +} + +/** + * Generate the routes file to be used in Vue files. + * + * @returns {Promise} + */ +function routes() { + return new Promise((resolve, reject) => { + exec('php artisan ziggy:generate resources/assets/scripts/helpers/ziggy.js', {}, (err, stdout, stderr) => { + return err ? reject(err) : resolve({ stdout, stderr }); + }); + }) +} + /** * Cleanup unused versions of hashed assets. */ @@ -82,9 +109,12 @@ function clean() { } exports.clean = clean; +exports.i18n = i18n; +exports.routes = routes; exports.styles = styles; exports.scripts = scripts; exports.watch = watch; +gulp.task('components', gulp.parallel(i18n, routes)); gulp.task('scripts', gulp.series(clean, scripts)); -gulp.task('default', gulp.series(clean, styles, scripts)); +gulp.task('default', gulp.series(clean, i18n, routes, styles, scripts)); diff --git a/package.json b/package.json index a790e437f..1eea87689 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "build:filemanager": "./node_modules/babel-cli/bin/babel.js public/themes/pterodactyl/js/frontend/files/src --source-maps --out-file public/themes/pterodactyl/js/frontend/files/filemanager.min.js", "watch": "./node_modules/gulp-cli/bin/gulp.js watch", "build": "./node_modules/gulp-cli/bin/gulp.js default", + "build:components": "./node_modules/gulp-cli/bin/gulp.js components", "build:styles": "./node_modules/gulp-cli/bin/gulp.js styles", "build:scripts": "./node_modules/gulp-cli/bin/gulp.js scripts" }, From 7a1d73ba9ee4882bd78f19635735520448694676 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 2 Jun 2018 15:54:52 -0700 Subject: [PATCH 06/14] Let gulp build the necessary core files using artisan --- gulpfile.js | 32 +++++++++++++++++++++++++++++++- package.json | 1 + 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index a7bcf73ce..b843f890a 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -2,6 +2,7 @@ const babel = require('gulp-babel'); const concat = require('gulp-concat'); const cssmin = require('gulp-cssmin'); const del = require('del'); +const exec = require('child_process').exec; const gulp = require('gulp'); const gulpif = require('gulp-if'); const postcss = require('gulp-postcss'); @@ -74,6 +75,32 @@ function watch() { }, scripts)); } +/** + * Generate the language files to be consumed by front end. + * + * @returns {Promise} + */ +function i18n() { + return new Promise((resolve, reject) => { + exec('php artisan vue-i18n:generate', {}, (err, stdout, stderr) => { + return err ? reject(err) : resolve({ stdout, stderr }); + }) + }) +} + +/** + * Generate the routes file to be used in Vue files. + * + * @returns {Promise} + */ +function routes() { + return new Promise((resolve, reject) => { + exec('php artisan ziggy:generate resources/assets/scripts/helpers/ziggy.js', {}, (err, stdout, stderr) => { + return err ? reject(err) : resolve({ stdout, stderr }); + }); + }) +} + /** * Cleanup unused versions of hashed assets. */ @@ -82,9 +109,12 @@ function clean() { } exports.clean = clean; +exports.i18n = i18n; +exports.routes = routes; exports.styles = styles; exports.scripts = scripts; exports.watch = watch; +gulp.task('components', gulp.parallel(i18n, routes)); gulp.task('scripts', gulp.series(clean, scripts)); -gulp.task('default', gulp.series(clean, styles, scripts)); +gulp.task('default', gulp.series(clean, i18n, routes, styles, scripts)); diff --git a/package.json b/package.json index a790e437f..1eea87689 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "build:filemanager": "./node_modules/babel-cli/bin/babel.js public/themes/pterodactyl/js/frontend/files/src --source-maps --out-file public/themes/pterodactyl/js/frontend/files/filemanager.min.js", "watch": "./node_modules/gulp-cli/bin/gulp.js watch", "build": "./node_modules/gulp-cli/bin/gulp.js default", + "build:components": "./node_modules/gulp-cli/bin/gulp.js components", "build:styles": "./node_modules/gulp-cli/bin/gulp.js styles", "build:scripts": "./node_modules/gulp-cli/bin/gulp.js scripts" }, From 92c03d49534141c3ae46ae35f75e32f4b0d7b48c Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 2 Jun 2018 16:39:49 -0700 Subject: [PATCH 07/14] Add tests for password reset page functionality --- .../components/auth/ForgotPassword.vue | 6 ++- tests/Browser/Pages/LoginPage.php | 7 ++- .../ForgotPasswordProcessTest.php | 50 +++++++++++++++++++ tests/Browser/PterodactylBrowser.php | 32 ++++++++++++ 4 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 tests/Browser/Processes/Authentication/ForgotPasswordProcessTest.php diff --git a/resources/assets/scripts/components/auth/ForgotPassword.vue b/resources/assets/scripts/components/auth/ForgotPassword.vue index e06fa40b4..c224fd778 100644 --- a/resources/assets/scripts/components/auth/ForgotPassword.vue +++ b/resources/assets/scripts/components/auth/ForgotPassword.vue @@ -5,13 +5,14 @@ >
- - +

{{ $t('auth.forgot_password.label_help') }}

@@ -25,6 +26,7 @@
{{ $t('auth.go_to_login') }} diff --git a/tests/Browser/Pages/LoginPage.php b/tests/Browser/Pages/LoginPage.php index 06ec27b57..5fd42cbf6 100644 --- a/tests/Browser/Pages/LoginPage.php +++ b/tests/Browser/Pages/LoginPage.php @@ -15,10 +15,15 @@ class LoginPage extends BasePage public function elements() { return [ + '@email' => '#grid-email', '@username' => '#grid-username', '@password' => '#grid-password', '@loginButton' => '#grid-login-button', - '@forgotPassword' => 'a[aria-label="Forgot password"]', + '@submitButton' => 'button.btn.btn-jumbo[type="submit"]', + '@forgotPassword' => 'a[href="/auth/password"][aria-label="Forgot password"]', + '@goToLogin' => 'a[href="/auth/login"][aria-label="Go to login"]', + '@alertSuccess' => 'div[role="alert"].success > span.message', + '@alertDanger' => 'div[role="alert"].danger > span.message', ]; } } diff --git a/tests/Browser/Processes/Authentication/ForgotPasswordProcessTest.php b/tests/Browser/Processes/Authentication/ForgotPasswordProcessTest.php new file mode 100644 index 000000000..ab8c9bc8a --- /dev/null +++ b/tests/Browser/Processes/Authentication/ForgotPasswordProcessTest.php @@ -0,0 +1,50 @@ +browse(function (PterodactylBrowser $browser) { + $browser->visit(new LoginPage) + ->assertSee(trans('auth.forgot_password.label')) + ->click('@forgotPassword') + ->waitForLocation('/auth/password') + ->assertFocused('@email') + ->assertSeeIn('.input-open > p.text-xs', trans('auth.forgot_password.label_help')) + ->assertSeeIn('@submitButton', trans('auth.forgot_password.button')) + ->type('@email', 'unassociated@example.com') + ->assertSeeIn('@goToLogin', trans('auth.go_to_login')) + ->press('@submitButton') + ->waitForLocation('/auth/login') + ->assertSeeIn('div[role="alert"].success > span.message', 'We have e-mailed your password reset link!') + ->assertFocused('@username') + ->assertValue('@username', 'unassociated@example.com'); + }); + } + + /** + * Test that you can type in your email address and then click forgot password and have + * the email maintained on the new page. + */ + public function testEmailCarryover() + { + $this->browse(function (PterodactylBrowser $browser) { + $browser->visit(new LoginPage) + ->type('@username', 'dane@example.com') + ->click('@forgotPassword') + ->waitForLocation('/auth/password') + ->assertFocused('@email') + ->assertValue('@email', 'dane@example.com'); + }); + } +} diff --git a/tests/Browser/PterodactylBrowser.php b/tests/Browser/PterodactylBrowser.php index ba08a0707..57a790844 100644 --- a/tests/Browser/PterodactylBrowser.php +++ b/tests/Browser/PterodactylBrowser.php @@ -3,7 +3,39 @@ namespace Pterodactyl\Tests\Browser; use Laravel\Dusk\Browser; +use Illuminate\Support\Str; +use PHPUnit\Framework\Assert as PHPUnit; class PterodactylBrowser extends Browser { + /** + * Perform a case insensitive search for a string in the body. + * + * @param string $text + * @return \Pterodactyl\Tests\Browser\PterodactylBrowser + */ + public function assertSee($text) + { + return $this->assertSeeIn('', $text); + } + + /** + * Perform a case insensitive search for a string in a given selector. + * + * @param string $selector + * @param string $text + * @return \Pterodactyl\Tests\Browser\PterodactylBrowser + */ + public function assertSeeIn($selector, $text) + { + $fullSelector = $this->resolver->format($selector); + $element = $this->resolver->findOrFail($selector); + + PHPUnit::assertTrue( + Str::contains(mb_strtolower($element->getText()), mb_strtolower($text)), + "Did not see expected text [{$text}] within element [{$fullSelector}] using case-insensitive search." + ); + + return $this; + } } From 4209be021e0c74623a2f723308bc806e9f14a635 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 2 Jun 2018 16:59:16 -0700 Subject: [PATCH 08/14] Add handlers for non-successful responses from the panel --- resources/assets/scripts/bootstrap.js | 2 +- .../assets/scripts/components/auth/ForgotPassword.vue | 4 ++++ resources/assets/scripts/components/auth/LoginForm.vue | 9 ++++++++- .../assets/scripts/components/auth/ResetPassword.vue | 4 ++++ .../assets/scripts/components/auth/TwoFactorForm.vue | 4 ++++ 5 files changed, 21 insertions(+), 2 deletions(-) diff --git a/resources/assets/scripts/bootstrap.js b/resources/assets/scripts/bootstrap.js index 8d2009067..f29ff0528 100644 --- a/resources/assets/scripts/bootstrap.js +++ b/resources/assets/scripts/bootstrap.js @@ -17,8 +17,8 @@ try { */ window.axios = require('axios'); - window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; +window.axios.defaults.headers.common['Accept'] = 'application/json'; /** * Next we will register the CSRF Token as a common header with Axios so that diff --git a/resources/assets/scripts/components/auth/ForgotPassword.vue b/resources/assets/scripts/components/auth/ForgotPassword.vue index e06fa40b4..0c72b884c 100644 --- a/resources/assets/scripts/components/auth/ForgotPassword.vue +++ b/resources/assets/scripts/components/auth/ForgotPassword.vue @@ -68,6 +68,10 @@ email: this.$props.email, }) .then(function (response) { + if (!(response.data instanceof Object)) { + throw new Error('An error was encountered while processing this request.'); + } + self.$data.submitDisabled = false; self.$data.showSpinner = false; self.success(response.data.status); diff --git a/resources/assets/scripts/components/auth/LoginForm.vue b/resources/assets/scripts/components/auth/LoginForm.vue index 07236d550..0028db4e2 100644 --- a/resources/assets/scripts/components/auth/LoginForm.vue +++ b/resources/assets/scripts/components/auth/LoginForm.vue @@ -81,6 +81,12 @@ password: this.$props.user.password, }) .then(function (response) { + // If there is a 302 redirect or some other odd behavior (basically, response that isnt + // in JSON format) throw an error and don't try to continue with the login. + if (!(response.data instanceof Object)) { + throw new Error('An error was encountered while processing this request.'); + } + if (response.data.complete) { return window.location = '/'; } @@ -92,6 +98,8 @@ .catch(function (err) { self.$props.user.password = ''; self.$data.showSpinner = false; + self.$refs.password.focus(); + if (!err.response) { return console.error(err); } @@ -101,7 +109,6 @@ response.data.errors.forEach(function (error) { self.error(error.detail); }); - self.$refs.password.focus(); } }); }, diff --git a/resources/assets/scripts/components/auth/ResetPassword.vue b/resources/assets/scripts/components/auth/ResetPassword.vue index 2a7cf17a6..cda6716ac 100644 --- a/resources/assets/scripts/components/auth/ResetPassword.vue +++ b/resources/assets/scripts/components/auth/ResetPassword.vue @@ -93,6 +93,10 @@ token: this.$props.token, }) .then(function (response) { + if (!(response.data instanceof Object)) { + throw new Error('An error was encountered while processing this login.'); + } + return window.location = response.data.redirect_to; }) .catch(function (err) { diff --git a/resources/assets/scripts/components/auth/TwoFactorForm.vue b/resources/assets/scripts/components/auth/TwoFactorForm.vue index 27d2b2282..fb51090f3 100644 --- a/resources/assets/scripts/components/auth/TwoFactorForm.vue +++ b/resources/assets/scripts/components/auth/TwoFactorForm.vue @@ -49,6 +49,10 @@ authentication_code: this.$data.code, }) .then(function (response) { + if (!(response.data instanceof Object)) { + throw new Error('An error was encountered while processing this login.'); + } + window.location = response.data.intended; }) .catch(function (err) { From dec969bf9f6e1d4b28904923be44e0f965bdb8c4 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 2 Jun 2018 17:01:54 -0700 Subject: [PATCH 09/14] Fix checkpoint behavior to only work when a token is provided --- resources/assets/scripts/app.js | 2 +- resources/assets/scripts/components/auth/TwoFactorForm.vue | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/resources/assets/scripts/app.js b/resources/assets/scripts/app.js index 180df2a40..1a6b9476c 100644 --- a/resources/assets/scripts/app.js +++ b/resources/assets/scripts/app.js @@ -35,7 +35,7 @@ const router = new VueRouter({ routes: [ { name: 'login', path: '/auth/login', component: Login }, { name: 'forgot-password', path: '/auth/password', component: Login }, - { name: 'checkpoint', path: '/checkpoint', component: Login }, + { name: 'checkpoint', path: '/auth/checkpoint', component: Login }, { name: 'reset-password', path: '/auth/password/reset/:token', diff --git a/resources/assets/scripts/components/auth/TwoFactorForm.vue b/resources/assets/scripts/components/auth/TwoFactorForm.vue index fb51090f3..84a0461d5 100644 --- a/resources/assets/scripts/components/auth/TwoFactorForm.vue +++ b/resources/assets/scripts/components/auth/TwoFactorForm.vue @@ -37,6 +37,10 @@ }; }, mounted: function () { + if ((this.$route.query.token || '').length < 1) { + return this.$router.push({ name: 'login' }); + } + this.$refs.code.focus(); }, methods: { From 0d56ed19a7b79beb81cfe5c0829a7a8972542cda Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 2 Jun 2018 17:15:04 -0700 Subject: [PATCH 10/14] Fix flash margins on login page --- resources/assets/scripts/components/auth/Login.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/assets/scripts/components/auth/Login.vue b/resources/assets/scripts/components/auth/Login.vue index efda62d2e..a2fadfbba 100644 --- a/resources/assets/scripts/components/auth/Login.vue +++ b/resources/assets/scripts/components/auth/Login.vue @@ -1,6 +1,6 @@ @@ -59,15 +29,17 @@ import { ServerCollection } from '../../models/server'; import _ from 'lodash'; import Flash from '../Flash'; + import ServerBox from './ServerBox'; export default { name: 'dashboard', - components: { Flash }, + components: { ServerBox, Flash }, data: function () { return { loading: true, search: '', servers: new ServerCollection, + resources: {}, } }, @@ -92,6 +64,7 @@ .then(response => { this.servers = new ServerCollection; response.data.data.forEach(obj => { + this.resources[obj.attributes.uuid] = { cpu: 0, memory: 0 }; this.servers.add(obj.attributes); }); diff --git a/resources/assets/scripts/components/dashboard/ServerBox.vue b/resources/assets/scripts/components/dashboard/ServerBox.vue new file mode 100644 index 000000000..a4f099849 --- /dev/null +++ b/resources/assets/scripts/components/dashboard/ServerBox.vue @@ -0,0 +1,50 @@ + + + From 02b29a66eaacae160686ec56d31ad6e9e5e3fd06 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 2 Jun 2018 19:08:53 -0700 Subject: [PATCH 12/14] Use client API to get resource use for a server --- .../Controllers/Base/DashboardController.php | 54 ------------- .../Api/Client/StatsTransformer.php | 7 ++ .../components/dashboard/Dashboard.vue | 41 ++++++++-- .../components/dashboard/ServerBox.vue | 75 +++++++++++++++++-- routes/api-client.php | 2 +- routes/base.php | 2 - 6 files changed, 113 insertions(+), 68 deletions(-) delete mode 100644 app/Http/Controllers/Base/DashboardController.php diff --git a/app/Http/Controllers/Base/DashboardController.php b/app/Http/Controllers/Base/DashboardController.php deleted file mode 100644 index 8351b037a..000000000 --- a/app/Http/Controllers/Base/DashboardController.php +++ /dev/null @@ -1,54 +0,0 @@ -repository = $repository; - } - - public function servers(Request $request) - { - $servers = $this->repository->setSearchTerm($request->input('query'))->filterUserAccessServers( - $request->user(), User::FILTER_LEVEL_ALL - ); - - $data = []; - foreach ($servers->items() as $server) { - $cleaned = collect($server)->only([ - 'uuidShort', - 'uuid', - 'name', - 'cpu', - 'memory', - ]); - - $data[] = array_merge($cleaned->toArray(), [ - 'allocation' => [ - 'ip' => $server->allocation->ip, - 'port' => $server->allocation->port, - ], - 'node_name' => $server->node->name, - ]); - } - - return response()->json($data); - } -} diff --git a/app/Transformers/Api/Client/StatsTransformer.php b/app/Transformers/Api/Client/StatsTransformer.php index 01d8e3f20..d3e66eb9a 100644 --- a/app/Transformers/Api/Client/StatsTransformer.php +++ b/app/Transformers/Api/Client/StatsTransformer.php @@ -3,6 +3,8 @@ namespace Pterodactyl\Transformers\Api\Client; use Pterodactyl\Models\Server; +use GuzzleHttp\Exception\RequestException; +use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface; class StatsTransformer extends BaseClientTransformer @@ -36,6 +38,8 @@ class StatsTransformer extends BaseClientTransformer * * @param \Pterodactyl\Models\Server $model * @return array + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException */ public function transform(Server $model) { @@ -61,7 +65,10 @@ class StatsTransformer extends BaseClientTransformer 'disk' => [ 'current' => round(object_get($object, 'proc.disk.used', 0)), 'limit' => floatval($model->disk), + 'io' => $model->io, ], + 'installed' => $model->installed === 1, + 'suspended' => (bool) $model->suspended, ]; } diff --git a/resources/assets/scripts/components/dashboard/Dashboard.vue b/resources/assets/scripts/components/dashboard/Dashboard.vue index 587d3dddb..6359534c7 100644 --- a/resources/assets/scripts/components/dashboard/Dashboard.vue +++ b/resources/assets/scripts/components/dashboard/Dashboard.vue @@ -19,7 +19,6 @@ v-for="(server, index) in servers.models" v-bind:key="index" v-bind:server="server" - v-bind:resources="resources[server.uuid]" />
@@ -39,14 +38,26 @@ loading: true, search: '', servers: new ServerCollection, - resources: {}, } }, - mounted: function () { + /** + * Start loading the servers before the DOM $.el is created. + */ + created: function () { this.loadServers(); }, + /** + * Once the page is mounted set a function to run every 5 seconds that will + * iterate through the visible servers and fetch their resource usage. + */ + mounted: function () { + setInterval(() => { + this.servers.each(this.getResourceUse) + }, 10000); + }, + methods: { /** * Load the user's servers and render them onto the dashboard. @@ -64,8 +75,9 @@ .then(response => { this.servers = new ServerCollection; response.data.data.forEach(obj => { - this.resources[obj.attributes.uuid] = { cpu: 0, memory: 0 }; - this.servers.add(obj.attributes); + this.getResourceUse( + this.servers.add(obj.attributes) + ); }); if (this.servers.models.length === 0) { @@ -93,6 +105,25 @@ onChange: _.debounce(function () { this.loadServers(this.$data.search); }, 500), + + /** + * Get resource usage for an individual server for rendering purposes. + * + * @param {Server} server + */ + getResourceUse: function (server) { + window.axios.get(this.route('api.client.servers.resources', { server: server.identifier })) + .then(response => { + if (!(response.data instanceof Object)) { + throw new Error('Received an invalid response object back from status endpoint.'); + } + + window.events.$emit(`server:${server.uuid}::resources`, response.data.attributes); + }) + .catch(err => { + console.error(err); + }); + }, } }; diff --git a/resources/assets/scripts/components/dashboard/ServerBox.vue b/resources/assets/scripts/components/dashboard/ServerBox.vue index a4f099849..b422f0503 100644 --- a/resources/assets/scripts/components/dashboard/ServerBox.vue +++ b/resources/assets/scripts/components/dashboard/ServerBox.vue @@ -2,7 +2,7 @@
-
+
@@ -19,11 +19,11 @@
- {{ resources.cpu > 0 ? resources.cpu : '—' }} + {{ cpu > 0 ? cpu : '—' }} %
- {{ resources.memory > 0 ? resources.memory : '—' }} + {{ memory > 0 ? memory : '—' }} Mb
@@ -38,13 +38,76 @@ diff --git a/routes/api-client.php b/routes/api-client.php index 23c79fd40..c8bfbc78d 100644 --- a/routes/api-client.php +++ b/routes/api-client.php @@ -23,7 +23,7 @@ Route::get('/', 'ClientController@index')->name('api.client.index'); Route::group(['prefix' => '/servers/{server}', 'middleware' => [AuthenticateClientAccess::class]], function () { Route::get('/', 'Servers\ServerController@index')->name('api.client.servers.view'); Route::get('/utilization', 'Servers\ResourceUtilizationController@index') - ->middleware(['throttle:15,1']) + ->middleware(['throttle:20,1']) ->name('api.client.servers.resources'); Route::post('/command', 'Servers\CommandController@index')->name('api.client.servers.command'); diff --git a/routes/base.php b/routes/base.php index 3c2a67829..4d86bada9 100644 --- a/routes/base.php +++ b/routes/base.php @@ -7,8 +7,6 @@ * https://opensource.org/licenses/MIT */ Route::get('/', 'IndexController@index')->name('index'); -Route::get('/dashboard/servers', 'DashboardController@servers')->name('dashboard.servers'); -Route::get('/status/{server}', 'IndexController@status')->name('index.status'); /* |-------------------------------------------------------------------------- From be5a9108f99c10b96674dc5fca6dc788d8cecfbc Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 2 Jun 2018 22:23:54 -0700 Subject: [PATCH 13/14] Dont refresh server statuses unless the page is active Reduces the number of polling requests happening on the server by only sending those requests if the user is actively viewing the dashboard. There was no point in updating the resource usage if no one is viewing it. After 30 seconds away from the window when a user comes back it will update instantenously, otherwise it'll just update after 5 seconds. --- package.json | 2 + .../components/dashboard/Dashboard.vue | 43 +++++++++++++++++-- .../components/dashboard/ServerBox.vue | 2 +- yarn.lock | 14 ++++++ 4 files changed, 57 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index d200e145a..b78502eec 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "jquery": "^3.3.1", "jwt-decode": "^2.2.0", "lodash": "^4.17.5", + "luxon": "^1.2.1", "postcss": "^6.0.21", "postcss-import": "^11.1.0", "postcss-preset-env": "^3.4.0", @@ -35,6 +36,7 @@ "vue": "^2.5.7", "vue-axios": "^2.1.1", "vue-devtools": "^3.1.9", + "vue-feather-icons": "^4.7.1", "vue-loader": "^14.2.2", "vue-mc": "^0.2.4", "vue-router": "^3.0.1", diff --git a/resources/assets/scripts/components/dashboard/Dashboard.vue b/resources/assets/scripts/components/dashboard/Dashboard.vue index 6359534c7..8f1997fde 100644 --- a/resources/assets/scripts/components/dashboard/Dashboard.vue +++ b/resources/assets/scripts/components/dashboard/Dashboard.vue @@ -25,6 +25,7 @@ diff --git a/resources/assets/scripts/components/dashboard/ServerBox.vue b/resources/assets/scripts/components/dashboard/ServerBox.vue index b422f0503..53de4d6ee 100644 --- a/resources/assets/scripts/components/dashboard/ServerBox.vue +++ b/resources/assets/scripts/components/dashboard/ServerBox.vue @@ -24,7 +24,7 @@
{{ memory > 0 ? memory : '—' }} - Mb + MB
diff --git a/yarn.lock b/yarn.lock index a37252566..e422ce0e6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -505,6 +505,10 @@ babel-helper-replace-supers@^6.24.1: babel-traverse "^6.24.1" babel-types "^6.24.1" +babel-helper-vue-jsx-merge-props@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-2.0.3.tgz#22aebd3b33902328e513293a8e4992b384f9f1b6" + babel-helpers@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2" @@ -3424,6 +3428,10 @@ lru-cache@^4.0.1, lru-cache@^4.1.1: pseudomap "^1.0.2" yallist "^2.1.2" +luxon@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.2.1.tgz#5c4948d141939c2b2820f0c3a99276932245efb1" + macaddress@^0.2.8: version "0.2.8" resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12" @@ -5985,6 +5993,12 @@ vue-devtools@^3.1.9: version "3.1.9" resolved "https://registry.yarnpkg.com/vue-devtools/-/vue-devtools-3.1.9.tgz#283b458c6853f569a987da0092e7c52e8243a436" +vue-feather-icons@^4.7.1: + version "4.7.1" + resolved "https://registry.yarnpkg.com/vue-feather-icons/-/vue-feather-icons-4.7.1.tgz#d8c55fbee7c9ad59689ebbaf07ad1e2f1e5c37da" + dependencies: + babel-helper-vue-jsx-merge-props "^2.0.2" + vue-hot-reload-api@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.0.tgz#97976142405d13d8efae154749e88c4e358cf926" From ee9a34716d2e7389eb246bf3927820f5655efffb Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 2 Jun 2018 23:15:10 -0700 Subject: [PATCH 14/14] Add navigation to vue, improve responsiveness of the design --- .../scripts/components/core/Navigation.vue | 42 ++++++++++++++++++ .../components/dashboard/Dashboard.vue | 44 ++++++++++--------- resources/assets/scripts/routes.js | 2 +- .../styles/components/miscellaneous.css | 6 +-- .../assets/styles/components/navigation.css | 22 +++++++++- resources/assets/styles/main.css | 8 +++- .../pterodactyl/templates/base/core.blade.php | 34 +------------- .../pterodactyl/templates/wrapper.blade.php | 12 ++--- tailwind.js | 1 + 9 files changed, 104 insertions(+), 67 deletions(-) create mode 100644 resources/assets/scripts/components/core/Navigation.vue diff --git a/resources/assets/scripts/components/core/Navigation.vue b/resources/assets/scripts/components/core/Navigation.vue new file mode 100644 index 000000000..701fada65 --- /dev/null +++ b/resources/assets/scripts/components/core/Navigation.vue @@ -0,0 +1,42 @@ + + + diff --git a/resources/assets/scripts/components/dashboard/Dashboard.vue b/resources/assets/scripts/components/dashboard/Dashboard.vue index 8f1997fde..59b1bdb0f 100644 --- a/resources/assets/scripts/components/dashboard/Dashboard.vue +++ b/resources/assets/scripts/components/dashboard/Dashboard.vue @@ -1,26 +1,29 @@ @@ -30,10 +33,11 @@ import _ from 'lodash'; import Flash from '../Flash'; import ServerBox from './ServerBox'; + import Navigation from '../core/Navigation'; export default { name: 'dashboard', - components: { ServerBox, Flash }, + components: { Navigation, ServerBox, Flash }, data: function () { return { backgroundedAt: DateTime.local(), diff --git a/resources/assets/scripts/routes.js b/resources/assets/scripts/routes.js index df2440e3f..0affefe2f 100644 --- a/resources/assets/scripts/routes.js +++ b/resources/assets/scripts/routes.js @@ -17,7 +17,7 @@ export const routes = [ } }, - { name : 'index', path: '/', component: Dashboard }, + { name : 'dashboard', path: '/', component: Dashboard }, { name : 'account', path: '/account', component: Account }, { name : 'account.api', path: '/account/api', component: Account }, { name : 'account.security', path: '/account/security', component: Account }, diff --git a/resources/assets/styles/components/miscellaneous.css b/resources/assets/styles/components/miscellaneous.css index 23bec3473..ad52cbbb5 100644 --- a/resources/assets/styles/components/miscellaneous.css +++ b/resources/assets/styles/components/miscellaneous.css @@ -49,11 +49,7 @@ code { @apply .pb-4; @screen smx { - @apply .w-1/2 .pr-4; - - &:nth-of-type(2n) { - padding-right: 0; - } + @apply .w-full; } @screen md { diff --git a/resources/assets/styles/components/navigation.css b/resources/assets/styles/components/navigation.css index cc1146c4d..16f0f6dcc 100644 --- a/resources/assets/styles/components/navigation.css +++ b/resources/assets/styles/components/navigation.css @@ -4,11 +4,18 @@ & > .logo { @apply .mx-8 .font-sans .font-thin .text-2xl .text-white .inline-block .pt-2; + + & a { + color: inherit; + text-decoration: none; + } + + @screen xsx { + @apply .hidden + } } & > .menu { - @apply .float-right .mx-8 .inline-block; - & > ul { @apply .list-reset; & > li { @@ -19,10 +26,21 @@ &:hover { @apply .bg-blue-dark; + } + & .feather { + @apply .h-4; } } } } + + @screen xsx { + @apply .w-full .text-center; + } + + @screen sm { + @apply .float-right .mx-8 .inline-block; + } } } diff --git a/resources/assets/styles/main.css b/resources/assets/styles/main.css index cfbd00666..4a42cd267 100644 --- a/resources/assets/styles/main.css +++ b/resources/assets/styles/main.css @@ -25,5 +25,11 @@ * Assorted Other CSS */ body { - @apply .font-sans; + @apply .font-sans; +} + +.container { + @screen xsx { + @apply .px-2; + } } diff --git a/resources/themes/pterodactyl/templates/base/core.blade.php b/resources/themes/pterodactyl/templates/base/core.blade.php index b9ad7ebb3..14caa41fc 100644 --- a/resources/themes/pterodactyl/templates/base/core.blade.php +++ b/resources/themes/pterodactyl/templates/base/core.blade.php @@ -1,40 +1,8 @@ @extends('templates/wrapper') -@section('above-container') - -@endsection - @section('container') -
+

{!! trans('strings.copyright', ['year' => date('Y')]) !!}

diff --git a/resources/themes/pterodactyl/templates/wrapper.blade.php b/resources/themes/pterodactyl/templates/wrapper.blade.php index 81bb3a0ee..cb5bee759 100644 --- a/resources/themes/pterodactyl/templates/wrapper.blade.php +++ b/resources/themes/pterodactyl/templates/wrapper.blade.php @@ -16,11 +16,13 @@ @include('layouts.scripts') - @yield('above-container') -
- @yield('container') -
- @yield('below-container') + @section('content') + @yield('above-container') +
+ @yield('container') +
+ @yield('below-container') + @show @section('scripts') {!! $asset->js('assets/scripts/bundle.js') !!} @show diff --git a/tailwind.js b/tailwind.js index 93b293244..922789e2a 100644 --- a/tailwind.js +++ b/tailwind.js @@ -172,6 +172,7 @@ module.exports = { 'lg': '992px', 'xl': '1200px', + 'xsx': {'max': '575px'}, 'smx': {'max': '767px'}, 'mdx': {'max': '991px'}, 'lgx': {'max': '1999px'},