Merge branch 'feature/vuejs' into feature/vue-serverview
This commit is contained in:
commit
e65854c8c2
|
@ -0,0 +1,26 @@
|
||||||
|
APP_ENV=local
|
||||||
|
APP_DEBUG=false
|
||||||
|
APP_KEY=NDWgIKKi9ovNK1PXZpzfNVSBdfCXGb5i
|
||||||
|
APP_JWT_KEY=test1234
|
||||||
|
APP_TIMEZONE=America/Los_Angeles
|
||||||
|
APP_URL=http://pterodactyl.local
|
||||||
|
|
||||||
|
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_CONNECTION=testing
|
||||||
|
TESTING_DB_HOST=services.pterodactyl.local
|
||||||
|
TESTING_DB_DATABASE=panel_test
|
||||||
|
TESTING_DB_USERNAME=panel_test
|
||||||
|
TESTING_DB_PASSWORD=Test1234
|
|
@ -1,54 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Base;
|
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Pterodactyl\Models\User;
|
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
|
||||||
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
|
|
||||||
|
|
||||||
class DashboardController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface
|
|
||||||
*/
|
|
||||||
private $repository;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DashboardController constructor.
|
|
||||||
*
|
|
||||||
* @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository
|
|
||||||
*/
|
|
||||||
public function __construct(ServerRepositoryInterface $repository)
|
|
||||||
{
|
|
||||||
$this->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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,6 +3,8 @@
|
||||||
namespace Pterodactyl\Transformers\Api\Client;
|
namespace Pterodactyl\Transformers\Api\Client;
|
||||||
|
|
||||||
use Pterodactyl\Models\Server;
|
use Pterodactyl\Models\Server;
|
||||||
|
use GuzzleHttp\Exception\RequestException;
|
||||||
|
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
|
||||||
use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface;
|
use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface;
|
||||||
|
|
||||||
class StatsTransformer extends BaseClientTransformer
|
class StatsTransformer extends BaseClientTransformer
|
||||||
|
@ -36,6 +38,8 @@ class StatsTransformer extends BaseClientTransformer
|
||||||
*
|
*
|
||||||
* @param \Pterodactyl\Models\Server $model
|
* @param \Pterodactyl\Models\Server $model
|
||||||
* @return array
|
* @return array
|
||||||
|
*
|
||||||
|
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
||||||
*/
|
*/
|
||||||
public function transform(Server $model)
|
public function transform(Server $model)
|
||||||
{
|
{
|
||||||
|
@ -61,7 +65,10 @@ class StatsTransformer extends BaseClientTransformer
|
||||||
'disk' => [
|
'disk' => [
|
||||||
'current' => round(object_get($object, 'proc.disk.used', 0)),
|
'current' => round(object_get($object, 'proc.disk.used', 0)),
|
||||||
'limit' => floatval($model->disk),
|
'limit' => floatval($model->disk),
|
||||||
|
'io' => $model->io,
|
||||||
],
|
],
|
||||||
|
'installed' => $model->installed === 1,
|
||||||
|
'suspended' => (bool) $model->suspended,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,7 @@
|
||||||
"filp/whoops": "^2.1",
|
"filp/whoops": "^2.1",
|
||||||
"friendsofphp/php-cs-fixer": "^2.11.1",
|
"friendsofphp/php-cs-fixer": "^2.11.1",
|
||||||
"fzaninotto/faker": "^1.6",
|
"fzaninotto/faker": "^1.6",
|
||||||
|
"laravel/dusk": "^3.0",
|
||||||
"martinlindhe/laravel-vue-i18n-generator": "^0.1.28",
|
"martinlindhe/laravel-vue-i18n-generator": "^0.1.28",
|
||||||
"mockery/mockery": "^1.0",
|
"mockery/mockery": "^1.0",
|
||||||
"nunomaduro/collision": "^2.0",
|
"nunomaduro/collision": "^2.0",
|
||||||
|
@ -68,6 +69,7 @@
|
||||||
},
|
},
|
||||||
"autoload-dev": {
|
"autoload-dev": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
"Pterodactyl\\Tests\\Browser\\": "tests/Browser",
|
||||||
"Pterodactyl\\Tests\\Integration\\": "tests/Integration",
|
"Pterodactyl\\Tests\\Integration\\": "tests/Integration",
|
||||||
"Tests\\": "tests/"
|
"Tests\\": "tests/"
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "f84af54d009a128472ca7e19a50fccf8",
|
"content-hash": "069ebb3ec35c8b309b129189106ad45a",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "appstract/laravel-blade-directives",
|
"name": "appstract/laravel-blade-directives",
|
||||||
|
@ -4692,6 +4692,66 @@
|
||||||
],
|
],
|
||||||
"time": "2017-07-22T11:58:36+00:00"
|
"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",
|
"name": "filp/whoops",
|
||||||
"version": "2.1.14",
|
"version": "2.1.14",
|
||||||
|
@ -5002,6 +5062,67 @@
|
||||||
],
|
],
|
||||||
"time": "2016-02-11T16:21:17+00:00"
|
"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",
|
"name": "martinlindhe/laravel-vue-i18n-generator",
|
||||||
"version": "0.1.28",
|
"version": "0.1.28",
|
||||||
|
|
32
gulpfile.js
32
gulpfile.js
|
@ -2,6 +2,7 @@ const babel = require('gulp-babel');
|
||||||
const concat = require('gulp-concat');
|
const concat = require('gulp-concat');
|
||||||
const cssmin = require('gulp-cssmin');
|
const cssmin = require('gulp-cssmin');
|
||||||
const del = require('del');
|
const del = require('del');
|
||||||
|
const exec = require('child_process').exec;
|
||||||
const gulp = require('gulp');
|
const gulp = require('gulp');
|
||||||
const gulpif = require('gulp-if');
|
const gulpif = require('gulp-if');
|
||||||
const postcss = require('gulp-postcss');
|
const postcss = require('gulp-postcss');
|
||||||
|
@ -85,6 +86,32 @@ function watch() {
|
||||||
}, scripts));
|
}, scripts));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the language files to be consumed by front end.
|
||||||
|
*
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
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<any>}
|
||||||
|
*/
|
||||||
|
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.
|
* Cleanup unused versions of hashed assets.
|
||||||
*/
|
*/
|
||||||
|
@ -93,9 +120,12 @@ function clean() {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.clean = clean;
|
exports.clean = clean;
|
||||||
|
exports.i18n = i18n;
|
||||||
|
exports.routes = routes;
|
||||||
exports.styles = styles;
|
exports.styles = styles;
|
||||||
exports.scripts = scripts;
|
exports.scripts = scripts;
|
||||||
exports.watch = watch;
|
exports.watch = watch;
|
||||||
|
|
||||||
|
gulp.task('components', gulp.parallel(i18n, routes));
|
||||||
gulp.task('scripts', gulp.series(clean, scripts));
|
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));
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
"jquery": "^3.3.1",
|
"jquery": "^3.3.1",
|
||||||
"jwt-decode": "^2.2.0",
|
"jwt-decode": "^2.2.0",
|
||||||
"lodash": "^4.17.5",
|
"lodash": "^4.17.5",
|
||||||
|
"luxon": "^1.2.1",
|
||||||
"postcss": "^6.0.21",
|
"postcss": "^6.0.21",
|
||||||
"postcss-import": "^11.1.0",
|
"postcss-import": "^11.1.0",
|
||||||
"postcss-preset-env": "^3.4.0",
|
"postcss-preset-env": "^3.4.0",
|
||||||
|
@ -42,6 +43,7 @@
|
||||||
"vue": "^2.5.7",
|
"vue": "^2.5.7",
|
||||||
"vue-axios": "^2.1.1",
|
"vue-axios": "^2.1.1",
|
||||||
"vue-devtools": "^3.1.9",
|
"vue-devtools": "^3.1.9",
|
||||||
|
"vue-feather-icons": "^4.7.1",
|
||||||
"vue-loader": "^14.2.2",
|
"vue-loader": "^14.2.2",
|
||||||
"vue-mc": "^0.2.4",
|
"vue-mc": "^0.2.4",
|
||||||
"vue-router": "^3.0.1",
|
"vue-router": "^3.0.1",
|
||||||
|
@ -58,6 +60,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",
|
"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",
|
"watch": "./node_modules/gulp-cli/bin/gulp.js watch",
|
||||||
"build": "./node_modules/gulp-cli/bin/gulp.js default",
|
"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:styles": "./node_modules/gulp-cli/bin/gulp.js styles",
|
||||||
"build:scripts": "./node_modules/gulp-cli/bin/gulp.js scripts"
|
"build:scripts": "./node_modules/gulp-cli/bin/gulp.js scripts"
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,9 @@
|
||||||
processIsolation="false"
|
processIsolation="false"
|
||||||
stopOnFailure="false">
|
stopOnFailure="false">
|
||||||
<testsuites>
|
<testsuites>
|
||||||
|
<testsuite name="Browser">
|
||||||
|
<directory suffix="Test.php">./tests/Browser/Processes</directory>
|
||||||
|
</testsuite>
|
||||||
<testsuite name="Integration">
|
<testsuite name="Integration">
|
||||||
<directory suffix="Test.php">./tests/Integration</directory>
|
<directory suffix="Test.php">./tests/Integration</directory>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
|
|
|
@ -17,8 +17,8 @@ try {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
window.axios = require('axios');
|
window.axios = require('axios');
|
||||||
|
|
||||||
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||||
|
window.axios.defaults.headers.common['Accept'] = 'application/json';
|
||||||
window.axios.defaults.headers.common['Authorization'] = 'Bearer ' + localStorage.token || '';
|
window.axios.defaults.headers.common['Authorization'] = 'Bearer ' + localStorage.token || '';
|
||||||
|
|
||||||
if (typeof phpdebugbar !== 'undefined') {
|
if (typeof phpdebugbar !== 'undefined') {
|
||||||
|
|
|
@ -5,13 +5,14 @@
|
||||||
>
|
>
|
||||||
<div class="flex flex-wrap -mx-3 mb-6">
|
<div class="flex flex-wrap -mx-3 mb-6">
|
||||||
<div class="input-open">
|
<div class="input-open">
|
||||||
<input class="input" id="grid-email" type="email" aria-labelledby="grid-email" ref="email" required
|
<input class="input" id="grid-email" type="email" aria-labelledby="grid-email-label" required
|
||||||
|
ref="email"
|
||||||
v-bind:class="{ 'has-content': email.length > 0 }"
|
v-bind:class="{ 'has-content': email.length > 0 }"
|
||||||
v-bind:readonly="showSpinner"
|
v-bind:readonly="showSpinner"
|
||||||
v-bind:value="email"
|
v-bind:value="email"
|
||||||
v-on:input="updateEmail($event)"
|
v-on:input="updateEmail($event)"
|
||||||
/>
|
/>
|
||||||
<label for="grid-email">{{ $t('strings.email') }}</label>
|
<label for="grid-email" id="grid-email-label">{{ $t('strings.email') }}</label>
|
||||||
<p class="text-grey-darker text-xs">{{ $t('auth.forgot_password.label_help') }}</p>
|
<p class="text-grey-darker text-xs">{{ $t('auth.forgot_password.label_help') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -25,6 +26,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="pt-6 text-center">
|
<div class="pt-6 text-center">
|
||||||
<router-link class="text-xs text-grey tracking-wide no-underline uppercase hover:text-grey-dark"
|
<router-link class="text-xs text-grey tracking-wide no-underline uppercase hover:text-grey-dark"
|
||||||
|
aria-label="Go to login"
|
||||||
:to="{ name: 'login' }"
|
:to="{ name: 'login' }"
|
||||||
>
|
>
|
||||||
{{ $t('auth.go_to_login') }}
|
{{ $t('auth.go_to_login') }}
|
||||||
|
@ -68,6 +70,10 @@
|
||||||
email: this.$props.email,
|
email: this.$props.email,
|
||||||
})
|
})
|
||||||
.then(function (response) {
|
.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.submitDisabled = false;
|
||||||
self.$data.showSpinner = false;
|
self.$data.showSpinner = false;
|
||||||
self.success(response.data.status);
|
self.success(response.data.status);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<flash/>
|
<flash container="mb-2"/>
|
||||||
<login-form
|
<login-form
|
||||||
v-if="this.$route.name === 'login'"
|
v-if="this.$route.name === 'login'"
|
||||||
v-bind:user="user"
|
v-bind:user="user"
|
||||||
|
|
|
@ -5,29 +5,30 @@
|
||||||
>
|
>
|
||||||
<div class="flex flex-wrap -mx-3 mb-6">
|
<div class="flex flex-wrap -mx-3 mb-6">
|
||||||
<div class="input-open">
|
<div class="input-open">
|
||||||
<input class="input" id="grid-username" type="text" name="user" aria-labelledby="grid-username" required
|
<input class="input" id="grid-username" type="text" name="user" aria-labelledby="grid-username-label" required
|
||||||
ref="email"
|
ref="email"
|
||||||
:class="{ 'has-content' : user.email.length > 0 }"
|
:class="{ 'has-content' : user.email.length > 0 }"
|
||||||
:readonly="showSpinner"
|
:readonly="showSpinner"
|
||||||
:value="user.email"
|
:value="user.email"
|
||||||
v-on:input="updateEmail($event)"
|
v-on:input="updateEmail($event)"
|
||||||
/>
|
/>
|
||||||
<label for="grid-username">{{ $t('strings.user_identifier') }}</label>
|
<label id="grid-username-label" for="grid-username">{{ $t('strings.user_identifier') }}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap -mx-3 mb-6">
|
<div class="flex flex-wrap -mx-3 mb-6">
|
||||||
<div class="input-open">
|
<div class="input-open">
|
||||||
<input class="input" id="grid-password" type="password" name="password" aria-labelledby="grid-password" required
|
<input class="input" id="grid-password" type="password" name="password" aria-labelledby="grid-password-label" required
|
||||||
ref="password"
|
ref="password"
|
||||||
:class="{ 'has-content' : user.password && user.password.length > 0 }"
|
:class="{ 'has-content' : user.password && user.password.length > 0 }"
|
||||||
:readonly="showSpinner"
|
:readonly="showSpinner"
|
||||||
v-model="user.password"
|
v-model="user.password"
|
||||||
/>
|
/>
|
||||||
<label for="grid-password">{{ $t('strings.password') }}</label>
|
<label id="grid-password-label" for="grid-password">{{ $t('strings.password') }}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button class="btn btn-blue btn-jumbo" type="submit" v-bind:disabled="showSpinner">
|
<button id="grid-login-button" class="btn btn-blue btn-jumbo" type="submit" aria-label="Log in"
|
||||||
|
v-bind:disabled="showSpinner">
|
||||||
<span class="spinner white" v-bind:class="{ hidden: ! showSpinner }"> </span>
|
<span class="spinner white" v-bind:class="{ hidden: ! showSpinner }"> </span>
|
||||||
<span v-bind:class="{ hidden: showSpinner }">
|
<span v-bind:class="{ hidden: showSpinner }">
|
||||||
{{ $t('auth.sign_in') }}
|
{{ $t('auth.sign_in') }}
|
||||||
|
@ -35,7 +36,7 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="pt-6 text-center">
|
<div class="pt-6 text-center">
|
||||||
<router-link class="text-xs text-grey tracking-wide no-underline uppercase hover:text-grey-dark"
|
<router-link class="text-xs text-grey tracking-wide no-underline uppercase hover:text-grey-dark" aria-label="Forgot password"
|
||||||
:to="{ name: 'forgot-password' }">
|
:to="{ name: 'forgot-password' }">
|
||||||
{{ $t('auth.forgot_password.label') }}
|
{{ $t('auth.forgot_password.label') }}
|
||||||
</router-link>
|
</router-link>
|
||||||
|
@ -81,6 +82,12 @@
|
||||||
password: this.$props.user.password,
|
password: this.$props.user.password,
|
||||||
})
|
})
|
||||||
.then(function (response) {
|
.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) {
|
if (response.data.complete) {
|
||||||
localStorage.setItem('token', response.data.token);
|
localStorage.setItem('token', response.data.token);
|
||||||
self.$store.dispatch('login');
|
self.$store.dispatch('login');
|
||||||
|
@ -94,6 +101,7 @@
|
||||||
.catch(function (err) {
|
.catch(function (err) {
|
||||||
self.$props.user.password = '';
|
self.$props.user.password = '';
|
||||||
self.$data.showSpinner = false;
|
self.$data.showSpinner = false;
|
||||||
|
self.$refs.password.focus();
|
||||||
self.$store.dispatch('logout');
|
self.$store.dispatch('logout');
|
||||||
|
|
||||||
if (!err.response) {
|
if (!err.response) {
|
||||||
|
@ -105,7 +113,6 @@
|
||||||
response.data.errors.forEach(function (error) {
|
response.data.errors.forEach(function (error) {
|
||||||
self.error(error.detail);
|
self.error(error.detail);
|
||||||
});
|
});
|
||||||
self.$refs.password.focus();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -93,6 +93,10 @@
|
||||||
token: this.$props.token,
|
token: this.$props.token,
|
||||||
})
|
})
|
||||||
.then(function (response) {
|
.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;
|
return window.location = response.data.redirect_to;
|
||||||
})
|
})
|
||||||
.catch(function (err) {
|
.catch(function (err) {
|
||||||
|
|
|
@ -37,6 +37,10 @@
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted: function () {
|
mounted: function () {
|
||||||
|
if ((this.$route.query.token || '').length < 1) {
|
||||||
|
return this.$router.push({ name: 'login' });
|
||||||
|
}
|
||||||
|
|
||||||
this.$refs.code.focus();
|
this.$refs.code.focus();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -49,8 +53,13 @@
|
||||||
authentication_code: this.$data.code,
|
authentication_code: this.$data.code,
|
||||||
})
|
})
|
||||||
.then(function (response) {
|
.then(function (response) {
|
||||||
|
if (!(response.data instanceof Object)) {
|
||||||
|
throw new Error('An error was encountered while processing this login.');
|
||||||
|
}
|
||||||
|
|
||||||
localStorage.setItem('token', response.data.token);
|
localStorage.setItem('token', response.data.token);
|
||||||
self.$store.dispatch('login');
|
self.$store.dispatch('login');
|
||||||
|
|
||||||
window.location = response.data.intended;
|
window.location = response.data.intended;
|
||||||
})
|
})
|
||||||
.catch(function (err) {
|
.catch(function (err) {
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
<template>
|
||||||
|
<div class="nav">
|
||||||
|
<div class="logo">
|
||||||
|
<router-link :to="{ name: 'dashboard' }">
|
||||||
|
Pterodactyl
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
<div class="menu">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<router-link :to="{ name: 'dashboard' }">
|
||||||
|
<server-icon aria-label="Server dashboard"/>
|
||||||
|
</router-link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<router-link :to="{ name: 'account' }">
|
||||||
|
<user-icon aria-label="Profile management"/>
|
||||||
|
</router-link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a :href="this.route('admin.index')">
|
||||||
|
<settings-icon aria-label="Administrative controls"/>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a :href="this.route('auth.logout')">
|
||||||
|
<log-out-icon aria-label="Sign out"/>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { LogOutIcon, ServerIcon, SettingsIcon, UserIcon } from 'vue-feather-icons'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'navigation',
|
||||||
|
components: { LogOutIcon, ServerIcon, SettingsIcon, UserIcon }
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -1,86 +1,71 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<flash container="mt-4"/>
|
<navigation/>
|
||||||
<div class="server-search animate fadein">
|
<div class="container">
|
||||||
<input type="text"
|
<flash container="mt-4"/>
|
||||||
:placeholder="$t('dashboard.index.search')"
|
<div class="server-search animate fadein">
|
||||||
@input="onChange"
|
<input type="text"
|
||||||
v-model="search"
|
:placeholder="$t('dashboard.index.search')"
|
||||||
ref="search"
|
@input="onChange"
|
||||||
/>
|
v-model="search"
|
||||||
</div>
|
ref="search"
|
||||||
<div v-if="this.loading" class="my-4 animate fadein">
|
/>
|
||||||
<div class="text-center h-16">
|
|
||||||
<span class="spinner spinner-xl"></span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div v-if="this.loading" class="my-4 animate fadein">
|
||||||
<transition-group class="w-full m-auto mt-4 animate fadein sm:flex flex-wrap content-start" v-else>
|
<div class="text-center h-16">
|
||||||
<div class="server-box animate fadein" :key="index" v-for="(server, index) in servers.models">
|
<span class="spinner spinner-xl"></span>
|
||||||
<router-link :to="{ name: 'server', params: { id: server.identifier }}" class="content">
|
</div>
|
||||||
<div class="float-right">
|
|
||||||
<div class="indicator"></div>
|
|
||||||
</div>
|
|
||||||
<div class="mb-4">
|
|
||||||
<div class="text-black font-bold text-xl">
|
|
||||||
{{ server.name }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mb-0 flex">
|
|
||||||
<div class="usage">
|
|
||||||
<div class="indicator-title">{{ $t('dashboard.index.cpu_title') }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="usage">
|
|
||||||
<div class="indicator-title">{{ $t('dashboard.index.memory_title') }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="mb-0 flex">
|
|
||||||
<div class="usage">
|
|
||||||
<div class="indicator-title">CPU</div>
|
|
||||||
</div>
|
|
||||||
<div class="usage">
|
|
||||||
<div class="indicator-title">Memory</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mb-4 flex text-center">
|
|
||||||
<div class="inline-block border border-grey-lighter border-l-0 p-4 flex-1">
|
|
||||||
<span class="font-bold text-xl">---</span>
|
|
||||||
<span class="font-light text-sm">%</span>
|
|
||||||
</div>
|
|
||||||
<div class="inline-block border border-grey-lighter border-l-0 border-r-0 p-4 flex-1">
|
|
||||||
<span class="font-bold text-xl">---</span>
|
|
||||||
<span class="font-light text-sm">Mb</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<div class="text-sm">
|
|
||||||
<p class="text-grey">{{ server.node }}</p>
|
|
||||||
<p class="text-grey-dark">{{ server.allocation.ip }}:{{ server.allocation.port }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</router-link>
|
|
||||||
</div>
|
</div>
|
||||||
</transition-group>
|
<transition-group class="w-full m-auto mt-4 animate fadein sm:flex flex-wrap content-start" v-else>
|
||||||
|
<server-box
|
||||||
|
v-for="(server, index) in servers.models"
|
||||||
|
v-bind:key="index"
|
||||||
|
v-bind:server="server"
|
||||||
|
/>
|
||||||
|
</transition-group>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
import { ServerCollection } from '../../models/server';
|
import { ServerCollection } from '../../models/server';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import Flash from '../Flash';
|
import Flash from '../Flash';
|
||||||
|
import ServerBox from './ServerBox';
|
||||||
|
import Navigation from '../core/Navigation';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'dashboard',
|
name: 'dashboard',
|
||||||
components: { Flash },
|
components: { Navigation, ServerBox, Flash },
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
|
backgroundedAt: DateTime.local(),
|
||||||
|
documentVisible: true,
|
||||||
loading: true,
|
loading: true,
|
||||||
search: '',
|
search: '',
|
||||||
servers: new ServerCollection,
|
servers: new ServerCollection,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted: function () {
|
/**
|
||||||
|
* Start loading the servers before the DOM $.el is created.
|
||||||
|
*/
|
||||||
|
created: function () {
|
||||||
this.loadServers();
|
this.loadServers();
|
||||||
|
|
||||||
|
document.addEventListener('visibilitychange', () => {
|
||||||
|
this.documentVisible = document.visibilityState === 'visible';
|
||||||
|
this._handleDocumentVisibilityChange(this.documentVisible);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 () {
|
||||||
|
this._iterateServerResourceUse();
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -100,7 +85,9 @@
|
||||||
.then(response => {
|
.then(response => {
|
||||||
this.servers = new ServerCollection;
|
this.servers = new ServerCollection;
|
||||||
response.data.data.forEach(obj => {
|
response.data.data.forEach(obj => {
|
||||||
this.servers.add(obj.attributes);
|
this.getResourceUse(
|
||||||
|
this.servers.add(obj.attributes)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.servers.models.length === 0) {
|
if (this.servers.models.length === 0) {
|
||||||
|
@ -128,6 +115,56 @@
|
||||||
onChange: _.debounce(function () {
|
onChange: _.debounce(function () {
|
||||||
this.loadServers(this.$data.search);
|
this.loadServers(this.$data.search);
|
||||||
}, 500),
|
}, 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);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterates over all of the active servers and gets their resource usage.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_iterateServerResourceUse: function (initialTimeout = 5000) {
|
||||||
|
window.setTimeout(() => {
|
||||||
|
if (this.documentVisible) {
|
||||||
|
return window.setTimeout(this._iterateServerResourceUse(), 5000);
|
||||||
|
}
|
||||||
|
}, initialTimeout);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle changes to document visibilty to keep server statuses updated properly.
|
||||||
|
*
|
||||||
|
* @param {Boolean} isVisible
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_handleDocumentVisibilityChange: function (isVisible) {
|
||||||
|
if (!isVisible) {
|
||||||
|
this.backgroundedAt = DateTime.local();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it has been more than 30 seconds since this window was put into the background
|
||||||
|
// lets go ahead and refresh all of the listed servers so that they have fresh stats.
|
||||||
|
const diff = DateTime.local().diff(this.backgroundedAt, 'seconds');
|
||||||
|
this._iterateServerResourceUse(diff.seconds > 30 ? 1 : 5000);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
<template>
|
||||||
|
<div class="server-box animate fadein">
|
||||||
|
<router-link :to="{ name: 'server', params: { id: server.identifier }}" class="content">
|
||||||
|
<div class="float-right">
|
||||||
|
<div class="indicator" :class="status"></div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<div class="text-black font-bold text-xl">
|
||||||
|
{{ server.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-0 flex">
|
||||||
|
<div class="usage">
|
||||||
|
<div class="indicator-title">{{ $t('dashboard.index.cpu_title') }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="usage">
|
||||||
|
<div class="indicator-title">{{ $t('dashboard.index.memory_title') }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4 flex text-center">
|
||||||
|
<div class="inline-block border border-grey-lighter border-l-0 p-4 flex-1">
|
||||||
|
<span class="font-bold text-xl">{{ cpu > 0 ? cpu : '—' }}</span>
|
||||||
|
<span class="font-light text-sm">%</span>
|
||||||
|
</div>
|
||||||
|
<div class="inline-block border border-grey-lighter border-l-0 border-r-0 p-4 flex-1">
|
||||||
|
<span class="font-bold text-xl">{{ memory > 0 ? memory : '—' }}</span>
|
||||||
|
<span class="font-light text-sm">MB</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="text-sm">
|
||||||
|
<p class="text-grey">{{ server.node }}</p>
|
||||||
|
<p class="text-grey-dark">{{ server.allocation.ip }}:{{ server.allocation.port }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import get from 'lodash/get';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'server-box',
|
||||||
|
props: {
|
||||||
|
server: { type: Object, required: true },
|
||||||
|
},
|
||||||
|
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
resources: undefined,
|
||||||
|
cpu: 0,
|
||||||
|
memory: 0,
|
||||||
|
status: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
created: function () {
|
||||||
|
window.events.$on(`server:${this.server.uuid}::resources`, data => {
|
||||||
|
this.resources = data;
|
||||||
|
this.status = this.getServerStatus();
|
||||||
|
|
||||||
|
this.memory = Number(get(data, 'memory.current', 0)).toFixed(0);
|
||||||
|
this.cpu = this._calculateCpu(
|
||||||
|
Number(get(data, 'cpu.current', 0)),
|
||||||
|
Number(this.server.limits.cpu)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* Set the CSS to use for displaying the server's current status.
|
||||||
|
*/
|
||||||
|
getServerStatus: function () {
|
||||||
|
if (!(this.resources instanceof Object)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.resources.installed || this.resources.suspended) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (this.resources.state) {
|
||||||
|
case 'off':
|
||||||
|
return 'offline';
|
||||||
|
case 'on':
|
||||||
|
case 'starting':
|
||||||
|
case 'stopping':
|
||||||
|
return 'online';
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the CPU usage for a given server relative to their set maximum.
|
||||||
|
*
|
||||||
|
* @param {Number} current
|
||||||
|
* @param {Number} max
|
||||||
|
* @return {Number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_calculateCpu: function (current, max) {
|
||||||
|
if (max === 0) {
|
||||||
|
return parseFloat(current.toFixed(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseFloat((current / max * 100).toFixed(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -17,7 +17,7 @@ import {
|
||||||
export const routes = [
|
export const routes = [
|
||||||
{ name: 'login', path: '/auth/login', component: Login },
|
{ name: 'login', path: '/auth/login', component: Login },
|
||||||
{ name: 'forgot-password', path: '/auth/password', 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',
|
name: 'reset-password',
|
||||||
path: '/auth/password/reset/:token',
|
path: '/auth/password/reset/:token',
|
||||||
|
@ -27,7 +27,7 @@ export const routes = [
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
{ name : 'index', path: '/', component: Dashboard },
|
{ name : 'dashboard', path: '/', component: Dashboard },
|
||||||
{ name : 'account', path: '/account', component: Account },
|
{ name : 'account', path: '/account', component: Account },
|
||||||
{ name : 'account.api', path: '/account/api', component: Account },
|
{ name : 'account.api', path: '/account/api', component: Account },
|
||||||
{ name : 'account.security', path: '/account/security', component: Account },
|
{ name : 'account.security', path: '/account/security', component: Account },
|
||||||
|
|
|
@ -49,11 +49,7 @@ code {
|
||||||
@apply .block .pb-4 .no-underline;
|
@apply .block .pb-4 .no-underline;
|
||||||
|
|
||||||
@screen smx {
|
@screen smx {
|
||||||
@apply .w-1/2 .pr-4;
|
@apply .w-full;
|
||||||
|
|
||||||
&:nth-of-type(2n) {
|
|
||||||
padding-right: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@screen md {
|
@screen md {
|
||||||
|
|
|
@ -24,11 +24,18 @@
|
||||||
|
|
||||||
& > .logo {
|
& > .logo {
|
||||||
@apply .mx-8 .font-sans .font-thin .text-2xl .text-white .inline-block .pt-2;
|
@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 {
|
& > .menu {
|
||||||
@apply .float-right .mx-8 .inline-block;
|
|
||||||
|
|
||||||
& > ul {
|
& > ul {
|
||||||
@apply .list-reset;
|
@apply .list-reset;
|
||||||
& > li {
|
& > li {
|
||||||
|
@ -39,11 +46,22 @@
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@apply .bg-blue-dark;
|
@apply .bg-blue-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .feather {
|
||||||
|
@apply .h-4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@screen xsx {
|
||||||
|
@apply .w-full .text-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@screen sm {
|
||||||
|
@apply .float-right .mx-8 .inline-block;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
|
|
|
@ -26,5 +26,11 @@
|
||||||
* Assorted Other CSS
|
* Assorted Other CSS
|
||||||
*/
|
*/
|
||||||
body {
|
body {
|
||||||
@apply .font-sans;
|
@apply .font-sans;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
@screen xsx {
|
||||||
|
@apply .px-2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,58 +1,12 @@
|
||||||
@extends('templates/wrapper')
|
@extends('templates/wrapper')
|
||||||
|
|
||||||
@section('above-container')
|
|
||||||
<header class="bg-blue text-white text-xl rounded-b fixed pin-t w-full z-40 shadow-md">
|
|
||||||
<div class="container h-16 mx-auto flex">
|
|
||||||
<img class="h-12 mt-2 mr-3" src="/assets/img/pterodactyl-flat.svg">
|
|
||||||
<div class="py-6">PTERODACTYL</div>
|
|
||||||
<div class="flex-grow"></div>
|
|
||||||
<nav class="nav text-lg">
|
|
||||||
<router-link to="/"><font-awesome-icon class="mr-2" fixed-with icon="server"></font-awesome-icon>Servers</router-link>
|
|
||||||
<a href="#"><font-awesome-icon class="mr-2" fixed-with icon="cogs"></font-awesome-icon>Admin</a>
|
|
||||||
<a href="#"><font-awesome-icon class="mr-2" fixed-with icon="user"></font-awesome-icon>schrej</a>
|
|
||||||
<a href="#"><font-awesome-icon fixed-with icon="sign-out-alt"></font-awesome-icon></a>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<div class="h-16 mb-6"></div>
|
|
||||||
{{--<div class="nav">
|
|
||||||
<div class="logo">
|
|
||||||
Pterodactyl
|
|
||||||
</div>
|
|
||||||
<div class="menu">
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<router-link to="/">
|
|
||||||
<span>Your Servers</span>
|
|
||||||
</router-link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#">
|
|
||||||
<span>Admin</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#">
|
|
||||||
<span>dane</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{{ route('auth.logout') }}">
|
|
||||||
<span>Logout</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>--}}
|
|
||||||
@endsection
|
|
||||||
|
|
||||||
@section('container')
|
@section('container')
|
||||||
<router-view></router-view>
|
<router-view></router-view>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@section('below-container')
|
@section('below-container')
|
||||||
<div class="flex-grow"></div>
|
<div class="flex-grow"></div>
|
||||||
<div class="container py-4">
|
<div class="w-full m-auto mt-0 container">
|
||||||
<p class="text-right text-grey-dark text-xs">
|
<p class="text-right text-grey-dark text-xs">
|
||||||
{!! trans('strings.copyright', ['year' => date('Y')]) !!}
|
{!! trans('strings.copyright', ['year' => date('Y')]) !!}
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -24,14 +24,14 @@
|
||||||
|
|
||||||
@include('layouts.scripts')
|
@include('layouts.scripts')
|
||||||
</head>
|
</head>
|
||||||
<body class="{{ $css['body'] ?? 'bg-white' }}">
|
<body class="{{ $css['body'] ?? 'bg-grey-lighter' }}">
|
||||||
<div id="pterodactyl" class="flex flex-col min-h-screen">
|
@section('content')
|
||||||
@yield('above-container')
|
@yield('above-container')
|
||||||
<div class="container">
|
<div id="pterodactyl" class="flex flex-col min-h-screen">
|
||||||
@yield('container')
|
@yield('container')
|
||||||
</div>
|
</div>
|
||||||
@yield('below-container')
|
@yield('below-container')
|
||||||
</div>
|
@show
|
||||||
@section('scripts')
|
@section('scripts')
|
||||||
{!! $asset->js('assets/scripts/bundle.js') !!}
|
{!! $asset->js('assets/scripts/bundle.js') !!}
|
||||||
@show
|
@show
|
||||||
|
|
|
@ -23,7 +23,7 @@ Route::get('/', 'ClientController@index')->name('api.client.index');
|
||||||
Route::group(['prefix' => '/servers/{server}', 'middleware' => [AuthenticateClientAccess::class]], function () {
|
Route::group(['prefix' => '/servers/{server}', 'middleware' => [AuthenticateClientAccess::class]], function () {
|
||||||
Route::get('/', 'Servers\ServerController@index')->name('api.client.servers.view');
|
Route::get('/', 'Servers\ServerController@index')->name('api.client.servers.view');
|
||||||
Route::get('/utilization', 'Servers\ResourceUtilizationController@index')
|
Route::get('/utilization', 'Servers\ResourceUtilizationController@index')
|
||||||
->middleware(['throttle:15,1'])
|
->middleware(['throttle:20,1'])
|
||||||
->name('api.client.servers.resources');
|
->name('api.client.servers.resources');
|
||||||
|
|
||||||
Route::post('/command', 'Servers\CommandController@index')->name('api.client.servers.command');
|
Route::post('/command', 'Servers\CommandController@index')->name('api.client.servers.command');
|
||||||
|
|
|
@ -7,8 +7,6 @@
|
||||||
* https://opensource.org/licenses/MIT
|
* https://opensource.org/licenses/MIT
|
||||||
*/
|
*/
|
||||||
Route::get('/', 'IndexController@index')->name('index');
|
Route::get('/', 'IndexController@index')->name('index');
|
||||||
Route::get('/dashboard/servers', 'DashboardController@servers')->name('dashboard.servers');
|
|
||||||
Route::get('/status/{server}', 'IndexController@status')->name('index.status');
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
@ -172,6 +172,7 @@ module.exports = {
|
||||||
'lg': '992px',
|
'lg': '992px',
|
||||||
'xl': '1200px',
|
'xl': '1200px',
|
||||||
|
|
||||||
|
'xsx': {'max': '575px'},
|
||||||
'smx': {'max': '767px'},
|
'smx': {'max': '767px'},
|
||||||
'mdx': {'max': '991px'},
|
'mdx': {'max': '991px'},
|
||||||
'lgx': {'max': '1999px'},
|
'lgx': {'max': '1999px'},
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
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;
|
||||||
|
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||||
|
|
||||||
|
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.
|
||||||
|
*
|
||||||
|
* @return \Facebook\WebDriver\Remote\RemoteWebDriver
|
||||||
|
*/
|
||||||
|
protected function driver()
|
||||||
|
{
|
||||||
|
$options = (new ChromeOptions)->addArguments([
|
||||||
|
'--disable-gpu',
|
||||||
|
'--disable-infobars',
|
||||||
|
]);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Pterodactyl\Tests\Browser\Pages;
|
||||||
|
|
||||||
|
use Laravel\Dusk\Page;
|
||||||
|
|
||||||
|
abstract class BasePage extends Page
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function siteElements()
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Pterodactyl\Tests\Browser\Pages;
|
||||||
|
|
||||||
|
class LoginPage extends BasePage
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function url(): string
|
||||||
|
{
|
||||||
|
return '/auth/login';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function elements()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'@email' => '#grid-email',
|
||||||
|
'@username' => '#grid-username',
|
||||||
|
'@password' => '#grid-password',
|
||||||
|
'@loginButton' => '#grid-login-button',
|
||||||
|
'@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',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Pterodactyl\Tests\Browser\Processes\Authentication;
|
||||||
|
|
||||||
|
use Pterodactyl\Tests\Browser\BrowserTestCase;
|
||||||
|
use Pterodactyl\Tests\Browser\Pages\LoginPage;
|
||||||
|
use Pterodactyl\Tests\Browser\PterodactylBrowser;
|
||||||
|
|
||||||
|
class ForgotPasswordProcessTest extends BrowserTestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Test that the password reset page works as expected and displays the expected
|
||||||
|
* success messages to the client when submitted.
|
||||||
|
*/
|
||||||
|
public function testResetPasswordWithInvalidAccount()
|
||||||
|
{
|
||||||
|
$this->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');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Pterodactyl\Tests\Browser\Processes\Authentication;
|
||||||
|
|
||||||
|
use Pterodactyl\Models\User;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Facebook\WebDriver\WebDriverKeys;
|
||||||
|
use Pterodactyl\Tests\Browser\BrowserTestCase;
|
||||||
|
use Pterodactyl\Tests\Browser\Pages\LoginPage;
|
||||||
|
use Pterodactyl\Tests\Browser\PterodactylBrowser;
|
||||||
|
|
||||||
|
class LoginProcessTest extends BrowserTestCase
|
||||||
|
{
|
||||||
|
private $user;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup tests.
|
||||||
|
*/
|
||||||
|
protected function setUp()
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
*
|
||||||
|
!.gitignore
|
|
@ -0,0 +1,2 @@
|
||||||
|
*
|
||||||
|
!.gitignore
|
14
yarn.lock
14
yarn.lock
|
@ -505,6 +505,10 @@ babel-helper-replace-supers@^6.24.1:
|
||||||
babel-traverse "^6.24.1"
|
babel-traverse "^6.24.1"
|
||||||
babel-types "^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:
|
babel-helpers@^6.24.1:
|
||||||
version "6.24.1"
|
version "6.24.1"
|
||||||
resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2"
|
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"
|
pseudomap "^1.0.2"
|
||||||
yallist "^2.1.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:
|
macaddress@^0.2.8:
|
||||||
version "0.2.8"
|
version "0.2.8"
|
||||||
resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12"
|
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"
|
version "3.1.9"
|
||||||
resolved "https://registry.yarnpkg.com/vue-devtools/-/vue-devtools-3.1.9.tgz#283b458c6853f569a987da0092e7c52e8243a436"
|
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:
|
vue-hot-reload-api@^2.2.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.0.tgz#97976142405d13d8efae154749e88c4e358cf926"
|
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.0.tgz#97976142405d13d8efae154749e88c4e358cf926"
|
||||||
|
|
Loading…
Reference in New Issue