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 00000000..1934ad3d --- /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 d2ac885f..e0b6dd39 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 bbb170e9..b55adab6 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 0b67ad6e..1bf73c4c 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 00000000..3e8ae90a --- /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 00000000..ba08a070 --- /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 1934ad3d..4c8e5052 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 3e8ae90a..3e7d08c0 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 07236d55..067fb929 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 3e7d08c0..dd96930a 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 00000000..7d8efb51 --- /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 00000000..1e0f8a0b --- /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 a7bcf73c..b843f890 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 29711e16..d200e145 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 a7bcf73c..b843f890 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 a790e437..1eea8768 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 a7bcf73c..b843f890 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 a790e437..1eea8768 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 e06fa40b..c224fd77 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 06ec27b5..5fd42cbf 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 00000000..ab8c9bc8 --- /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 ba08a070..57a79084 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 8d200906..f29ff052 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 e06fa40b..0c72b884 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 07236d55..0028db4e 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 2a7cf17a..cda6716a 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 27d2b228..fb51090f 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 180df2a4..1a6b9476 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 fb51090f..84a0461d 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 efda62d2..a2fadfbb 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 00000000..a4f09984 --- /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 8351b037..00000000 --- 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 01d8e3f2..d3e66eb9 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 587d3ddd..6359534c 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 a4f09984..b422f050 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 23c79fd4..c8bfbc78 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 3c2a6782..4d86bada 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 d200e145..b78502ee 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 6359534c..8f1997fd 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 b422f050..53de4d6e 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 a3725256..e422ce0e 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 00000000..701fada6 --- /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 8f1997fd..59b1bdb0 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 df2440e3..0affefe2 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 23bec347..ad52cbbb 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 cc1146c4..16f0f6dc 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 cfbd0066..4a42cd26 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 b9ad7ebb..14caa41f 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 81bb3a0e..cb5bee75 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 93b29324..922789e2 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'},